From 6e5eeca79bd158da9a982e25407afc3f8c0cc28f Mon Sep 17 00:00:00 2001 From: Andrea Magni Date: Wed, 27 May 2020 16:28:40 +0200 Subject: [PATCH] Develop to Master (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TXMLReader and TXMLWriter implementation (MBR and MBW mechanism for IXMLDocument and IXMLNode) * DataSetToXML now has a root node * Added shortcut class methods WriteDataSet for TDataSetWriterJSON and TDataSetWriterXML classes * IMPORTANT: Changes in the MessageBodyWriter selection mechanism. Affinity now comes with higher priority than QualityFactor. In other words, this means server side type matching comes before client side accept statement. Improved TMediaTypeList.Intersect (now properly considering WILDCARD also). Improved TMediaTypeList.GetQualityFactor (now properly considering WILDCARD also). TFDDataSetWriter is now decorated with Produces attributes only for APPLICATION_JSON_FIREDAC, APPLICATION_XML_FIREDAC and APPLICATION_OCTET_STREAM (removed APPLICATION_XML). This will let TDataSetWriterXML to jump in if registered. Added WriteDataSet shortcut method to TArrayFDDataSetWriter class. Refactoring. * TMARSFireDAC.LoadConnectionDefs now checks if a Connection Definition is already available before defining it. This means you can have a connection definition on your development machine (configured through FireDAC standard mechanisms such as FireDAC Explorer or ini files) and at the same time include a definition for deployment or for development machines where design time experience is not required. BEWARE: no exception is thrown so keep in mind a local definition (FireDAC Explorer / ini files) actually overrides what's defined in MARS Engine parameters/ini files. * Bugfix: TMARSFireDAC.InjectMacroValues and InjectParamValues assumed ACommand always assigned (but for example a TFDMemTable has Command = nil). Thanks Stuart Clennet for pointing this out. * Added FindWriter overload for specific Accept lookup, new TMARSMessageBodyWriter class (utility functions container for MBW) * Bugfix: TMARSNetClient implementations for LastCmdSuccess, ResponseStatusCode and ResponseText raised an exception if the FLastResponse object was not assigned. This can happen when connection troubles occur (thanks Stuart Clennet for pointing this out in this thread https://en.delphipraxis.net/topic/436-error-when-accessing-tmarsnetclient-responsestatuscode-when-endpoint-is-blacklisted/ ) * Bugifx #53: packages for 10.3 Rio are now correct * Bugfix #54: fixed TStringReader, added Encoding support (initial) for MBR and refactoring. * Bugfix #57: TJSONObjectHelper.ReadDateTimeValue ignores ADefault argument * Introduction of IMARSRequest and IMARSResponse Wrappers for TWebRequest and TWebResponse * * resolving conflicts to merge Brov84-develop * Added x-www-form-urlencoded support for MARS Resources (#56) * Added x-www-form-urlencoded for MARS Client Resources * Refactoring MARS URLEncoded form support Register new resource component Packages 10.3 Rio for URLEncoded form support MARS.Core.RequestAndResponse.Interfaces now added to MARS Utils package * + Delphi-Cross-Socket https://github.com/winddriver/Delphi-Cross-Socket * TMARSWebRequest and TMARSWebResponse wrap for Server.WebModule.pas * Removed hint for missing inline expansion * + MARS.DCS package * updated packages for 10.3 * BugFix: missing FireBeforeExecute in TMARSCustomClient (preventing anonyms added with RegisterBeforeExecute to fire when using regular component based calls, it was working for non-component based approach). Thanks @Davide675 for spotting this bug. * BugFix: missing FireBeforeExecute in TMARSCustomClient (preventing anonyms added with RegisterBeforeExecute to fire when using regular component based calls, it was working for non-component based approach). Thanks @Davide675 for spotting this bug. # Conflicts: # Source/MARS.Client.Client.pas * BugFix: wrong default for configuration file name when deployed as Apache module. Fixes #67, thanks @acarlomagno * Add Linux Server Delphi 10.2, 10.3 (#68) Great thanks! * Further refactoring to abstract from Web.HttpApp and Indy http layer BugFix: changes about RequiredAttribute in commit a0bcbce5c43dbb45648d89e6bc3a44b74e7996bd broke handling of multipart/form-data files (Demos/MultipartFormData), thanks @stuartclennett for pointing this out (https://en.delphipraxis.net/topic/243-handling-multipartformdata/) Refactoring * Add {$IFDEF TESTINSIGHT} (#69) Thanks @Tino-T :-) * MARS.mORMot Its not suported for Linux64 (#71) MARS.FireDAC suported for Linux64 MARS.JOSE suported for Linux64 * Updated Mustache demo to use IMARSResponse and IMARSRequest. Enabled CORS and added StatusCode for swagger endpoint. (#70) * JOSE JWT upgrade to latest version * Upgrade Delphi-Cross-Socket to the latest version * Fix JOSE packages Compilation issue with JOSEJWT JWT tests are now using a 32 char length dummy secret * Upgrade mORMot-JWT to the latest version * Authorization demo fixes: * dproj upgrade * compilation issues with BeforeHandleRequest handler * Set FDManager.SilentMode to True to avoid including Wait providers for FireDAC everywhere. * Added GetQueryParamCount and GetCookieParamCount to IMARSRequest * GitHub Social template for MARS * TFDDataSetWriter now defaults to JSON serializer when the match is with the Wildcard media type (before the first entry in TFDDataSetWriter.WriteTo method would apply, that was the XML serializer). * Develop (#75) * Updated Mustache demo to use IMARSResponse and IMARSRequest. Enabled CORS and added StatusCode for swagger endpoint. * Added last file needed to be independent of a separate DMustache library. * UniDAC support (initial commit): many thanks to ErtanK for the implementation. Added MARS.UniDAC package Added "UniDAC Basic" demo * WIP UniDAC support * UniDAC: connection definition handling (WIP) * UniDAC support: better handling of connection definitions (caching at startup), optimized by-name access; Added SQLite example in UniDAC Basic demo UniDAC dataset serialization up to TMemDataSet (and TVirtualTable) Fixed macro injection * UniDAC support: a SQLite connection has been added to UniDAC Basic/bin/UniDACBasicServerApplication.ini and some equivalent code using FireDAC has been added in Server.Resources.pas * + Missing MARSClient.CoreResource.rc for 103Rio package * Added the ability to setup Windows Service Name and DisplayName using MARS parameters. Just add: ServiceName=YourServiceName ServiceDisplayName=Your Service Display Name to the ini file (or whatever parameter's storage system you are using). * dproj upgrade Delphi 10.3.3 Rio * Fix ApplyCustomHeaders restored after rebase of develop onto master branch * Fix visiblity (caused warning) * Removed MARS.UniDAC package from MARS.groupproj * Develop (#78) * Updated Mustache demo to use IMARSResponse and IMARSRequest. Enabled CORS and added StatusCode for swagger endpoint. * Added last file needed to be independent of a separate DMustache library. * Implemented support for gzip compression * Reverted modifications to MARS.Core.Activation. * Revert "Implemented support for gzip compression" This reverts commit 57d52646ee34f91e6d0b1791ea7c7ff28da803b2. * Implementation for gzip compression. * Implementation for gzip compression. Co-authored-by: Bjørn Larsen * Changed GZip compression default to False in MARSTemplate * + MARSTemplateDCS demo * MARSTemplateDCS compilation * WIP MARSTemplateDCS * Issue #80: thanks to @grzegorzdeyk for pointing this out: Default encoding should be last call in GetEncodingName * + RemoteMic demo * Upgrade dproj 10.3.3 * Upgrade 10.4 Sydney Added packages for Delphi 10.4 Sydney Removed hints and warnings compiling with 10.4 Sydney * Upgrade Delphi 10.4 Sydney: Demos/MARSTemplate dprojs * Upgrade Delphi 10.4 Sydney: Demos/MARSTemplateDCS dprojs * Include definitions updated for Delphi 10.4 Sydney * Output directory updated for Delphi 10.4 Sydney (270) * Correction SetArrayLength (#83) Correction for Range check error on POST request on Linux https://github.com/andrea-magni/MARS/issues/81 Co-authored-by: Andrea Magni * Merge error after PR#83 Co-authored-by: Paolo Brovelli Co-authored-by: luiguik2 Co-authored-by: Tino-T Co-authored-by: Bjørn Larsen Co-authored-by: Bjørn Larsen Co-authored-by: Emmanuel Nocentini <64004621+meg02in@users.noreply.github.com> --- .../FMXClientAuthorization.dproj | 85 +- Demos/Authorization/MARSAuthorization.dproj | 8 +- Demos/Authorization/Server.Forms.Main.pas | 9 +- Demos/ContentTypes/Server.Resources.pas | 5 +- .../JsonDataObjectsApacheModule.dproj | 39 +- .../JsonDataObjectsConsole.dproj | 39 +- .../JsonDataObjects/JsonDataObjectsFMX.dproj | 123 +- .../JsonDataObjectsISAPI.dproj | 39 +- .../JsonDataObjects/JsonDataObjectsVCL.dproj | 39 +- Demos/JsonDataObjects/JsonDataObjectsVCL.res | Bin 59024 -> 59020 bytes .../JsonDataObjectsWinSvc.dproj | 61 +- Demos/JsonDataObjects/Server.Resources.pas | 13 +- Demos/MARSTemplate/MARSTemplateClient.dproj | 639 +- .../MARSTemplateServerApacheModule.dproj | 465 +- .../MARSTemplateServerApplication.dproj | 2 +- ...MARSTemplateServerConsoleApplication.dproj | 551 +- .../MARSTemplateServerDaemon.dproj | 519 +- .../MARSTemplateServerFMXApplication.dproj | 626 +- .../MARSTemplateServerISAPI.dproj | 3 +- .../MARSTemplateServerService.dproj | 475 +- Demos/MARSTemplate/Server.Ignition.pas | 40 +- Demos/MARSTemplate/Server.Service.pas | 3 + Demos/MARSTemplate/Server.WebModule.pas | 5 +- .../bin/MARSTemplateServerApplication.ini | 3 +- .../FMXClient.DataModules.Main.dfm | 25 + .../FMXClient.DataModules.Main.pas | 32 + .../FMXClient.DataModules.Main.vlb | 10 + .../MARSTemplateDCS/FMXClient.Forms.Main.fmx | 27 + .../MARSTemplateDCS/FMXClient.Forms.Main.pas | 34 + .../MARSTemplateDCS/FMXClient.Forms.Main.vlb | 26 + .../MARSTemplateDCS/MARSTemplateClientDCS.dpr | 21 + .../MARSTemplateClientDCS.dproj | 1423 ++ .../MARSTemplateDCSProjectGroup.groupproj | 96 + .../MARSTemplateServerDCSApplication.dpr | 16 + .../MARSTemplateServerDCSApplication.dproj | 1133 ++ ...ARSTemplateServerDCSConsoleApplication.dpr | 201 + ...STemplateServerDCSConsoleApplication.dproj | 1224 ++ .../MARSTemplateServerDCSDaemon.dpr | 19 + .../MARSTemplateServerDCSDaemon.dproj | 1211 ++ .../MARSTemplateServerDCSFMXApplication.dpr | 21 + .../MARSTemplateServerDCSFMXApplication.dproj | 1354 ++ .../MARSTemplateServerDCSService.dpr | 43 + .../MARSTemplateServerDCSService.dproj | 1210 ++ .../MARSTemplateServer_Icon.ico | Bin 0 -> 67646 bytes .../MARSTemplateDCS/Server.DCS.Forms.Main.dfm | 80 + .../MARSTemplateDCS/Server.DCS.Forms.Main.pas | 142 + .../MARSTemplateDCS/Server.FMX.Forms.Main.fmx | 66 + .../MARSTemplateDCS/Server.FMX.Forms.Main.pas | 97 + Demos/MARSTemplateDCS/Server.Forms.Main.dfm | 83 + Demos/MARSTemplateDCS/Server.Forms.Main.pas | 148 + Demos/MARSTemplateDCS/Server.Ignition.pas | 132 + Demos/MARSTemplateDCS/Server.Resources.pas | 48 + Demos/MARSTemplateDCS/Server.Service.dfm | 10 + Demos/MARSTemplateDCS/Server.Service.pas | 124 + Demos/MARSTemplateDCS/Server.WebModule.dfm | 12 + Demos/MARSTemplateDCS/Server.WebModule.pas | 57 + Demos/MARSTemplateDCS/ServerConst.pas | 42 + .../bin/MARSTemplateServerApplication.ini | 2 + .../MultipartFormDataServerApplication.dproj | 2 +- Demos/MultipartFormData/Server.Ignition.pas | 5 +- .../MARSMustacheServerApacheModule.dproj | 471 +- .../MARSMustacheServerApplication.dproj | 441 +- .../MARSMustacheServerApplication.res | Bin 59024 -> 59020 bytes ...MARSMustacheServerConsoleApplication.dproj | 453 +- .../MARSMustacheServerFMXApplication.dproj | 618 +- Demos/Mustache/MARSMustacheServerISAPI.dproj | 441 +- .../Mustache/MARSMustacheServerService.dproj | 463 +- Demos/Mustache/Server.Forms.Main.pas | 4 +- .../RemoteMic/FMXClient.DataModules.Main.dfm | 25 + .../RemoteMic/FMXClient.DataModules.Main.pas | 32 + .../RemoteMic/FMXClient.DataModules.Main.vlb | 10 + Demos/RemoteMic/FMXClient.Forms.Main.fmx | 27 + Demos/RemoteMic/FMXClient.Forms.Main.pas | 34 + Demos/RemoteMic/FMXClient.Forms.Main.vlb | 26 + Demos/RemoteMic/RemoteMicClient.dpr | 21 + Demos/RemoteMic/RemoteMicClient.dproj | 1423 ++ .../RemoteMic/RemoteMicProjectGroup.groupproj | 120 + .../RemoteMic/RemoteMicServerApacheModule.dpr | 54 + .../RemoteMicServerApacheModule.dproj | 985 ++ .../RemoteMic/RemoteMicServerApplication.dpr | 23 + .../RemoteMicServerApplication.dproj | 190 + .../RemoteMicServerConsoleApplication.dpr | 201 + .../RemoteMicServerConsoleApplication.dproj | 1211 ++ Demos/RemoteMic/RemoteMicServerDaemon.dpr | 21 + Demos/RemoteMic/RemoteMicServerDaemon.dproj | 1161 ++ .../RemoteMicServerFMXApplication.deployproj | 132 + .../RemoteMicServerFMXApplication.dpr | 21 + .../RemoteMicServerFMXApplication.dproj | 1376 ++ Demos/RemoteMic/RemoteMicServerISAPI.dpr | 30 + Demos/RemoteMic/RemoteMicServerISAPI.dproj | 152 + Demos/RemoteMic/RemoteMicServerService.dpr | 43 + Demos/RemoteMic/RemoteMicServerService.dproj | 1135 ++ Demos/RemoteMic/RemoteMicServer_Icon.ico | Bin 0 -> 67646 bytes Demos/RemoteMic/Server.FMX.Forms.Main.fmx | 66 + Demos/RemoteMic/Server.FMX.Forms.Main.pas | 97 + Demos/RemoteMic/Server.Forms.Main.dfm | 83 + Demos/RemoteMic/Server.Forms.Main.pas | 148 + Demos/RemoteMic/Server.Ignition.pas | 148 + Demos/RemoteMic/Server.Resources.pas | 119 + Demos/RemoteMic/Server.Service.dfm | 10 + Demos/RemoteMic/Server.Service.pas | 126 + Demos/RemoteMic/Server.WebModule.dfm | 12 + Demos/RemoteMic/Server.WebModule.pas | 57 + Demos/RemoteMic/ServerConst.pas | 42 + .../bin/RemoteMicServerApplication.ini | 4 + .../FMXClient.DataModules.Main.dfm | 25 + .../FMXClient.DataModules.Main.pas | 32 + .../FMXClient.DataModules.Main.vlb | 10 + Demos/UniDAC Basic/FMXClient.Forms.Main.fmx | 27 + Demos/UniDAC Basic/FMXClient.Forms.Main.pas | 34 + Demos/UniDAC Basic/FMXClient.Forms.Main.vlb | 26 + Demos/UniDAC Basic/Server.FMX.Forms.Main.fmx | 66 + Demos/UniDAC Basic/Server.FMX.Forms.Main.pas | 97 + Demos/UniDAC Basic/Server.Forms.Main.dfm | 83 + Demos/UniDAC Basic/Server.Forms.Main.pas | 148 + Demos/UniDAC Basic/Server.Ignition.pas | 149 + Demos/UniDAC Basic/Server.Resources.pas | 107 + Demos/UniDAC Basic/Server.Service.dfm | 10 + Demos/UniDAC Basic/Server.Service.pas | 123 + Demos/UniDAC Basic/Server.WebModule.dfm | 12 + Demos/UniDAC Basic/Server.WebModule.pas | 57 + Demos/UniDAC Basic/ServerConst.pas | 42 + Demos/UniDAC Basic/UniDACBasicClient.dpr | 21 + Demos/UniDAC Basic/UniDACBasicClient.dproj | 1259 ++ .../UniDACBasicProjectGroup.groupproj | 120 + .../UniDACBasicServerApacheModule.dpr | 54 + .../UniDACBasicServerApacheModule.dproj | 856 + .../UniDACBasicServerApplication.dpr | 23 + .../UniDACBasicServerApplication.dproj | 190 + .../UniDACBasicServerConsoleApplication.dpr | 201 + .../UniDACBasicServerConsoleApplication.dproj | 1049 ++ .../UniDAC Basic/UniDACBasicServerDaemon.dpr | 21 + .../UniDACBasicServerDaemon.dproj | 1008 ++ ...UniDACBasicServerFMXApplication.deployproj | 132 + .../UniDACBasicServerFMXApplication.dpr | 21 + .../UniDACBasicServerFMXApplication.dproj | 1212 ++ Demos/UniDAC Basic/UniDACBasicServerISAPI.dpr | 30 + .../UniDAC Basic/UniDACBasicServerISAPI.dproj | 152 + .../UniDAC Basic/UniDACBasicServerService.dpr | 43 + .../UniDACBasicServerService.dproj | 1006 ++ Demos/UniDAC Basic/UniDACBasicServer_Icon.ico | Bin 0 -> 67646 bytes Demos/UniDAC Basic/bin/MyDatabase.db | Bin 0 -> 24576 bytes .../bin/UniDACBasicServerApplication.ini | 26 + Packages/103Rio/MARS.Core.dpk | 11 +- Packages/103Rio/MARS.Core.dproj | 393 +- Packages/103Rio/MARS.Core.res | Bin 640 -> 640 bytes Packages/103Rio/MARS.DCS.dpk | 57 + Packages/103Rio/MARS.DCS.dproj | 1068 ++ Packages/103Rio/MARS.DCS.res | Bin 0 -> 640 bytes Packages/103Rio/MARS.DelphiRazor.dpk | 2 +- Packages/103Rio/MARS.DelphiRazor.dproj | 4 +- Packages/103Rio/MARS.FireDAC.dpk | 12 +- Packages/103Rio/MARS.FireDAC.dproj | 435 +- Packages/103Rio/MARS.FireDAC.res | Bin 664 -> 512 bytes Packages/103Rio/MARS.JOSE.dpk | 16 +- Packages/103Rio/MARS.JOSE.dproj | 444 +- Packages/103Rio/MARS.ReadersAndWriters.dpk | 12 +- Packages/103Rio/MARS.ReadersAndWriters.dproj | 1595 +- Packages/103Rio/MARS.ReadersAndWriters.res | Bin 724 -> 724 bytes Packages/103Rio/MARS.UniDAC.dpk | 47 + Packages/103Rio/MARS.UniDAC.dproj | 989 ++ Packages/103Rio/MARS.UniDAC.res | Bin 0 -> 652 bytes Packages/103Rio/MARS.Utils.dpk | 17 +- Packages/103Rio/MARS.Utils.dproj | 1622 +- Packages/103Rio/MARS.Utils.res | Bin 652 -> 652 bytes Packages/103Rio/MARS.groupproj | 18 +- Packages/103Rio/MARS.mORMot.dpk | 3 +- Packages/103Rio/MARS.mORMot.dproj | 417 +- Packages/103Rio/MARS.mORMot.res | Bin 652 -> 652 bytes Packages/103Rio/MARSClient.Core.dpk | 3 +- Packages/103Rio/MARSClient.Core.dproj | 439 +- Packages/103Rio/MARSClient.CoreDesign.dproj | 440 +- Packages/103Rio/MARSClient.CoreDesign.res | Bin 712 -> 512 bytes Packages/103Rio/MARSClient.CoreResource.rc | 12 + Packages/103Rio/MARSClient.FireDAC.dproj | 420 +- .../103Rio/MARSClient.FireDACDesign.dproj | 328 +- Packages/104Sydney/MARS.Core.dpk | 68 + Packages/104Sydney/MARS.Core.dproj | 770 + Packages/104Sydney/MARS.DCS.dpk | 57 + Packages/104Sydney/MARS.DCS.dproj | 1117 ++ Packages/104Sydney/MARS.FireDAC.dpk | 59 + Packages/104Sydney/MARS.FireDAC.dproj | 1062 ++ Packages/104Sydney/MARS.JOSE.dpk | 70 + Packages/104Sydney/MARS.JOSE.dproj | 1098 ++ Packages/104Sydney/MARS.ReadersAndWriters.dpk | 49 + .../104Sydney/MARS.ReadersAndWriters.dproj | 1070 ++ Packages/104Sydney/MARS.Utils.dpk | 59 + Packages/104Sydney/MARS.Utils.dproj | 1078 ++ Packages/104Sydney/MARS.groupproj | 108 + Packages/104Sydney/MARS.mORMot.dpk | 50 + Packages/104Sydney/MARS.mORMot.dproj | 1083 ++ Packages/104Sydney/MARSClient.Core.dpk | 57 + Packages/104Sydney/MARSClient.Core.dproj | 1118 ++ Packages/104Sydney/MARSClient.CoreDesign.dpk | 46 + .../104Sydney/MARSClient.CoreDesign.dproj | 1058 ++ Packages/104Sydney/MARSClient.FireDAC.dpk | 49 + Packages/104Sydney/MARSClient.FireDAC.dproj | 1044 ++ .../104Sydney/MARSClient.FireDACDesign.dpk | 46 + .../104Sydney/MARSClient.FireDACDesign.dproj | 1043 ++ Packages/104Sydney/MARSClient.groupproj | 84 + Source/MARS.Client.Client.Net.pas | 49 +- Source/MARS.Client.Client.pas | 20 + Source/MARS.Client.Register.pas | 4 +- .../MARS.Client.Resource.FormUrlEncoded.pas | 248 + .../MARS.Core.Activation.InjectionService.pas | 30 +- Source/MARS.Core.Activation.Interfaces.pas | 21 +- Source/MARS.Core.Activation.pas | 36 +- Source/MARS.Core.Attributes.pas | 142 +- Source/MARS.Core.Engine.pas | 27 +- Source/MARS.Core.JSON.pas | 2017 +-- Source/MARS.Core.MediaType.pas | 13 +- Source/MARS.Core.MessageBodyReader.pas | 71 +- Source/MARS.Core.MessageBodyReaders.pas | 150 +- Source/MARS.Core.MessageBodyWriter.pas | 60 +- Source/MARS.Core.MessageBodyWriters.pas | 77 +- ...ARS.Core.RequestAndResponse.Interfaces.pas | 76 + Source/MARS.Core.Response.pas | 17 +- Source/MARS.Core.Token.pas | 57 +- Source/MARS.Core.URL.pas | 15 +- Source/MARS.Core.Utils.pas | 116 +- .../MARS.Data.FireDAC.ReadersAndWriters.pas | 65 +- Source/MARS.Data.FireDAC.pas | 20 +- Source/MARS.Data.MessageBodyWriters.pas | 34 +- Source/MARS.Data.UniDAC.InjectionService.pas | 142 + Source/MARS.Data.UniDAC.ReadersAndWriters.pas | 177 + Source/MARS.Data.UniDAC.Utils.pas | 260 + Source/MARS.Data.UniDAC.pas | 721 + Source/MARS.Data.Utils.pas | 8 +- Source/MARS.JOSEJWT.Token.pas | 2 +- ...MARS.JsonDataObjects.ReadersAndWriters.pas | 4 +- Source/MARS.Rtti.Utils.pas | 2135 +-- Source/MARS.Utils.Parameters.pas | 40 +- Source/MARS.http.Server.DCS.pas | 568 + Source/MARS.http.Server.Indy.pas | 328 +- Source/MARS.inc | 26 +- ThirdParty/DCS/LICENSE | 165 + ThirdParty/DCS/Net/BSD.kqueue.pas | 114 + ThirdParty/DCS/Net/Linux.epoll.pas | 72 + .../DCS/Net/Net.CrossHttpMiddleware.pas | 222 + ThirdParty/DCS/Net/Net.CrossHttpParams.pas | 2005 +++ ThirdParty/DCS/Net/Net.CrossHttpRouter.pas | 418 + ThirdParty/DCS/Net/Net.CrossHttpServer.pas | 4792 ++++++ ThirdParty/DCS/Net/Net.CrossHttpUtils.pas | 1203 ++ ThirdParty/DCS/Net/Net.CrossServer.pas | 133 + ThirdParty/DCS/Net/Net.CrossSocket.Base.pas | 1757 ++ ThirdParty/DCS/Net/Net.CrossSocket.Epoll.pas | 986 ++ ThirdParty/DCS/Net/Net.CrossSocket.Iocp.pas | 765 + ThirdParty/DCS/Net/Net.CrossSocket.Kqueue.pas | 1030 ++ ThirdParty/DCS/Net/Net.CrossSocket.pas | 55 + ThirdParty/DCS/Net/Net.CrossSslDemoCert.pas | 69 + ThirdParty/DCS/Net/Net.CrossSslServer.pas | 142 + .../DCS/Net/Net.CrossSslSocket.Base.pas | 85 + .../DCS/Net/Net.CrossSslSocket.MbedTls.pas | 540 + .../DCS/Net/Net.CrossSslSocket.OpenSSL.pas | 507 + ThirdParty/DCS/Net/Net.CrossSslSocket.pas | 31 + .../DCS/Net/Net.CrossWebSocketServer.pas | 1067 ++ ThirdParty/DCS/Net/Net.MbedBIO.pas | 390 + ThirdParty/DCS/Net/Net.MbedTls.pas | 1641 ++ ThirdParty/DCS/Net/Net.OpenSSL.pas | 1377 ++ ThirdParty/DCS/Net/Net.Posix.inc | 42 + ThirdParty/DCS/Net/Net.RawSocket.pas | 189 + ThirdParty/DCS/Net/Net.SocketAPI.pas | 776 + ThirdParty/DCS/Net/Net.Winsock.inc | 1049 ++ ThirdParty/DCS/Net/Net.Winsock2.pas | 6755 ++++++++ ThirdParty/DCS/Net/Net.Wship6.pas | 590 + ThirdParty/DCS/README.en.md | 70 + ThirdParty/DCS/README.md | 74 + ThirdParty/DCS/Utils/Utils.DateTime.pas | 565 + ThirdParty/DCS/Utils/Utils.Logger.pas | 377 + ThirdParty/DCS/Utils/Utils.RegEx.pas | 101 + ThirdParty/DCS/Utils/Utils.Utils.pas | 492 + ....Cryptography.RSA.pas => JOSE.Builder.pas} | 22 +- .../Source/JOSE.Consumer.Validators.pas | 347 + .../delphi-jose-jwt/Source/JOSE.Consumer.pas | 554 + .../delphi-jose-jwt/Source/JOSE.Context.pas | 139 + .../delphi-jose-jwt/Source/JOSE.Core.Base.pas | 272 +- .../Source/JOSE.Core.Builder.pas | 126 +- .../Source/JOSE.Core.JWA.Compression.pas | 47 + .../Source/JOSE.Core.JWA.Encryption.pas | 70 + .../Source/JOSE.Core.JWA.Factory.pas | 163 + .../Source/JOSE.Core.JWA.Signing.pas | 306 + .../delphi-jose-jwt/Source/JOSE.Core.JWA.pas | 161 +- .../delphi-jose-jwt/Source/JOSE.Core.JWE.pas | 4 +- .../delphi-jose-jwt/Source/JOSE.Core.JWK.pas | 13 +- .../delphi-jose-jwt/Source/JOSE.Core.JWS.pas | 195 +- .../delphi-jose-jwt/Source/JOSE.Core.JWT.pas | 250 +- .../Source/JOSE.Core.Parts.pas | 38 +- .../Source/JOSE.Encoding.Base64.pas | 159 +- .../Source/JOSE.Hashing.HMAC.pas | 60 +- .../Source/JOSE.Signing.RSA.pas | 391 + .../Source/JOSE.Types.Arrays.pas | 273 + .../Source/JOSE.Types.Bytes.pas | 78 +- .../Source/JOSE.Types.JSON.pas | 251 + ThirdParty/mORMot/Source/SynCommons.pas | 10783 +++++-------- ThirdParty/mORMot/Source/SynCrypto.pas | 505 +- ThirdParty/mORMot/Source/SynEcc.pas | 62 +- ThirdParty/mORMot/Source/SynEcc32asm.inc | 2 +- ThirdParty/mORMot/Source/SynLZ.pas | 4 +- ThirdParty/mORMot/Source/SynMustache.pas | 1441 ++ ThirdParty/mORMot/Source/SynTable.pas | 13273 ++++++++++++++++ ThirdParty/mORMot/Source/Synopse.inc | 79 +- ThirdParty/mORMot/Source/SynopseCommit.inc | 2 +- Utils/Source/MARScmd/MARScmd_VCL.dproj | 474 +- Utils/Source/MARScmd/MARScmd_VCL.res | Bin 59532 -> 112124 bytes media/GitHubSocialTemplate.png | Bin 0 -> 575175 bytes media/GitHubSocialTemplate.xcf | Bin 0 -> 2601639 bytes tests/Tests.JWT.pas | 9 +- 307 files changed, 112007 insertions(+), 11810 deletions(-) create mode 100644 Demos/MARSTemplateDCS/FMXClient.DataModules.Main.dfm create mode 100644 Demos/MARSTemplateDCS/FMXClient.DataModules.Main.pas create mode 100644 Demos/MARSTemplateDCS/FMXClient.DataModules.Main.vlb create mode 100644 Demos/MARSTemplateDCS/FMXClient.Forms.Main.fmx create mode 100644 Demos/MARSTemplateDCS/FMXClient.Forms.Main.pas create mode 100644 Demos/MARSTemplateDCS/FMXClient.Forms.Main.vlb create mode 100644 Demos/MARSTemplateDCS/MARSTemplateClientDCS.dpr create mode 100644 Demos/MARSTemplateDCS/MARSTemplateClientDCS.dproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateDCSProjectGroup.groupproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dpr create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dpr create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dpr create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dpr create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dpr create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dproj create mode 100644 Demos/MARSTemplateDCS/MARSTemplateServer_Icon.ico create mode 100644 Demos/MARSTemplateDCS/Server.DCS.Forms.Main.dfm create mode 100644 Demos/MARSTemplateDCS/Server.DCS.Forms.Main.pas create mode 100644 Demos/MARSTemplateDCS/Server.FMX.Forms.Main.fmx create mode 100644 Demos/MARSTemplateDCS/Server.FMX.Forms.Main.pas create mode 100644 Demos/MARSTemplateDCS/Server.Forms.Main.dfm create mode 100644 Demos/MARSTemplateDCS/Server.Forms.Main.pas create mode 100644 Demos/MARSTemplateDCS/Server.Ignition.pas create mode 100644 Demos/MARSTemplateDCS/Server.Resources.pas create mode 100644 Demos/MARSTemplateDCS/Server.Service.dfm create mode 100644 Demos/MARSTemplateDCS/Server.Service.pas create mode 100644 Demos/MARSTemplateDCS/Server.WebModule.dfm create mode 100644 Demos/MARSTemplateDCS/Server.WebModule.pas create mode 100644 Demos/MARSTemplateDCS/ServerConst.pas create mode 100644 Demos/MARSTemplateDCS/bin/MARSTemplateServerApplication.ini create mode 100644 Demos/RemoteMic/FMXClient.DataModules.Main.dfm create mode 100644 Demos/RemoteMic/FMXClient.DataModules.Main.pas create mode 100644 Demos/RemoteMic/FMXClient.DataModules.Main.vlb create mode 100644 Demos/RemoteMic/FMXClient.Forms.Main.fmx create mode 100644 Demos/RemoteMic/FMXClient.Forms.Main.pas create mode 100644 Demos/RemoteMic/FMXClient.Forms.Main.vlb create mode 100644 Demos/RemoteMic/RemoteMicClient.dpr create mode 100644 Demos/RemoteMic/RemoteMicClient.dproj create mode 100644 Demos/RemoteMic/RemoteMicProjectGroup.groupproj create mode 100644 Demos/RemoteMic/RemoteMicServerApacheModule.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerApacheModule.dproj create mode 100644 Demos/RemoteMic/RemoteMicServerApplication.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerApplication.dproj create mode 100644 Demos/RemoteMic/RemoteMicServerConsoleApplication.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerConsoleApplication.dproj create mode 100644 Demos/RemoteMic/RemoteMicServerDaemon.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerDaemon.dproj create mode 100644 Demos/RemoteMic/RemoteMicServerFMXApplication.deployproj create mode 100644 Demos/RemoteMic/RemoteMicServerFMXApplication.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerFMXApplication.dproj create mode 100644 Demos/RemoteMic/RemoteMicServerISAPI.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerISAPI.dproj create mode 100644 Demos/RemoteMic/RemoteMicServerService.dpr create mode 100644 Demos/RemoteMic/RemoteMicServerService.dproj create mode 100644 Demos/RemoteMic/RemoteMicServer_Icon.ico create mode 100644 Demos/RemoteMic/Server.FMX.Forms.Main.fmx create mode 100644 Demos/RemoteMic/Server.FMX.Forms.Main.pas create mode 100644 Demos/RemoteMic/Server.Forms.Main.dfm create mode 100644 Demos/RemoteMic/Server.Forms.Main.pas create mode 100644 Demos/RemoteMic/Server.Ignition.pas create mode 100644 Demos/RemoteMic/Server.Resources.pas create mode 100644 Demos/RemoteMic/Server.Service.dfm create mode 100644 Demos/RemoteMic/Server.Service.pas create mode 100644 Demos/RemoteMic/Server.WebModule.dfm create mode 100644 Demos/RemoteMic/Server.WebModule.pas create mode 100644 Demos/RemoteMic/ServerConst.pas create mode 100644 Demos/RemoteMic/bin/RemoteMicServerApplication.ini create mode 100644 Demos/UniDAC Basic/FMXClient.DataModules.Main.dfm create mode 100644 Demos/UniDAC Basic/FMXClient.DataModules.Main.pas create mode 100644 Demos/UniDAC Basic/FMXClient.DataModules.Main.vlb create mode 100644 Demos/UniDAC Basic/FMXClient.Forms.Main.fmx create mode 100644 Demos/UniDAC Basic/FMXClient.Forms.Main.pas create mode 100644 Demos/UniDAC Basic/FMXClient.Forms.Main.vlb create mode 100644 Demos/UniDAC Basic/Server.FMX.Forms.Main.fmx create mode 100644 Demos/UniDAC Basic/Server.FMX.Forms.Main.pas create mode 100644 Demos/UniDAC Basic/Server.Forms.Main.dfm create mode 100644 Demos/UniDAC Basic/Server.Forms.Main.pas create mode 100644 Demos/UniDAC Basic/Server.Ignition.pas create mode 100644 Demos/UniDAC Basic/Server.Resources.pas create mode 100644 Demos/UniDAC Basic/Server.Service.dfm create mode 100644 Demos/UniDAC Basic/Server.Service.pas create mode 100644 Demos/UniDAC Basic/Server.WebModule.dfm create mode 100644 Demos/UniDAC Basic/Server.WebModule.pas create mode 100644 Demos/UniDAC Basic/ServerConst.pas create mode 100644 Demos/UniDAC Basic/UniDACBasicClient.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicClient.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicProjectGroup.groupproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerApacheModule.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerApacheModule.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerApplication.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerApplication.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerDaemon.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerDaemon.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerFMXApplication.deployproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerISAPI.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerISAPI.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServerService.dpr create mode 100644 Demos/UniDAC Basic/UniDACBasicServerService.dproj create mode 100644 Demos/UniDAC Basic/UniDACBasicServer_Icon.ico create mode 100644 Demos/UniDAC Basic/bin/MyDatabase.db create mode 100644 Demos/UniDAC Basic/bin/UniDACBasicServerApplication.ini create mode 100644 Packages/103Rio/MARS.DCS.dpk create mode 100644 Packages/103Rio/MARS.DCS.dproj create mode 100644 Packages/103Rio/MARS.DCS.res create mode 100644 Packages/103Rio/MARS.UniDAC.dpk create mode 100644 Packages/103Rio/MARS.UniDAC.dproj create mode 100644 Packages/103Rio/MARS.UniDAC.res create mode 100644 Packages/103Rio/MARSClient.CoreResource.rc create mode 100644 Packages/104Sydney/MARS.Core.dpk create mode 100644 Packages/104Sydney/MARS.Core.dproj create mode 100644 Packages/104Sydney/MARS.DCS.dpk create mode 100644 Packages/104Sydney/MARS.DCS.dproj create mode 100644 Packages/104Sydney/MARS.FireDAC.dpk create mode 100644 Packages/104Sydney/MARS.FireDAC.dproj create mode 100644 Packages/104Sydney/MARS.JOSE.dpk create mode 100644 Packages/104Sydney/MARS.JOSE.dproj create mode 100644 Packages/104Sydney/MARS.ReadersAndWriters.dpk create mode 100644 Packages/104Sydney/MARS.ReadersAndWriters.dproj create mode 100644 Packages/104Sydney/MARS.Utils.dpk create mode 100644 Packages/104Sydney/MARS.Utils.dproj create mode 100644 Packages/104Sydney/MARS.groupproj create mode 100644 Packages/104Sydney/MARS.mORMot.dpk create mode 100644 Packages/104Sydney/MARS.mORMot.dproj create mode 100644 Packages/104Sydney/MARSClient.Core.dpk create mode 100644 Packages/104Sydney/MARSClient.Core.dproj create mode 100644 Packages/104Sydney/MARSClient.CoreDesign.dpk create mode 100644 Packages/104Sydney/MARSClient.CoreDesign.dproj create mode 100644 Packages/104Sydney/MARSClient.FireDAC.dpk create mode 100644 Packages/104Sydney/MARSClient.FireDAC.dproj create mode 100644 Packages/104Sydney/MARSClient.FireDACDesign.dpk create mode 100644 Packages/104Sydney/MARSClient.FireDACDesign.dproj create mode 100644 Packages/104Sydney/MARSClient.groupproj create mode 100644 Source/MARS.Client.Resource.FormUrlEncoded.pas create mode 100644 Source/MARS.Core.RequestAndResponse.Interfaces.pas create mode 100644 Source/MARS.Data.UniDAC.InjectionService.pas create mode 100644 Source/MARS.Data.UniDAC.ReadersAndWriters.pas create mode 100644 Source/MARS.Data.UniDAC.Utils.pas create mode 100644 Source/MARS.Data.UniDAC.pas create mode 100644 Source/MARS.http.Server.DCS.pas create mode 100644 ThirdParty/DCS/LICENSE create mode 100644 ThirdParty/DCS/Net/BSD.kqueue.pas create mode 100644 ThirdParty/DCS/Net/Linux.epoll.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossHttpMiddleware.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossHttpParams.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossHttpRouter.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossHttpServer.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossHttpUtils.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossServer.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSocket.Base.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSocket.Epoll.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSocket.Iocp.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSocket.Kqueue.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSocket.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSslDemoCert.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSslServer.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSslSocket.Base.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSslSocket.MbedTls.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSslSocket.OpenSSL.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossSslSocket.pas create mode 100644 ThirdParty/DCS/Net/Net.CrossWebSocketServer.pas create mode 100644 ThirdParty/DCS/Net/Net.MbedBIO.pas create mode 100644 ThirdParty/DCS/Net/Net.MbedTls.pas create mode 100644 ThirdParty/DCS/Net/Net.OpenSSL.pas create mode 100644 ThirdParty/DCS/Net/Net.Posix.inc create mode 100644 ThirdParty/DCS/Net/Net.RawSocket.pas create mode 100644 ThirdParty/DCS/Net/Net.SocketAPI.pas create mode 100644 ThirdParty/DCS/Net/Net.Winsock.inc create mode 100644 ThirdParty/DCS/Net/Net.Winsock2.pas create mode 100644 ThirdParty/DCS/Net/Net.Wship6.pas create mode 100644 ThirdParty/DCS/README.en.md create mode 100644 ThirdParty/DCS/README.md create mode 100644 ThirdParty/DCS/Utils/Utils.DateTime.pas create mode 100644 ThirdParty/DCS/Utils/Utils.Logger.pas create mode 100644 ThirdParty/DCS/Utils/Utils.RegEx.pas create mode 100644 ThirdParty/DCS/Utils/Utils.Utils.pas rename ThirdParty/delphi-jose-jwt/Source/{JOSE.Cryptography.RSA.pas => JOSE.Builder.pas} (81%) create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.Validators.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Context.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Compression.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Encryption.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Factory.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Signing.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Signing.RSA.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Arrays.pas create mode 100644 ThirdParty/delphi-jose-jwt/Source/JOSE.Types.JSON.pas create mode 100644 ThirdParty/mORMot/Source/SynMustache.pas create mode 100644 ThirdParty/mORMot/Source/SynTable.pas create mode 100644 media/GitHubSocialTemplate.png create mode 100644 media/GitHubSocialTemplate.xcf diff --git a/Demos/Authorization/FMXClientAuthorization.dproj b/Demos/Authorization/FMXClientAuthorization.dproj index 3b2ed514..083c4ef7 100644 --- a/Demos/Authorization/FMXClientAuthorization.dproj +++ b/Demos/Authorization/FMXClientAuthorization.dproj @@ -1,7 +1,7 @@  {0546214C-5B2E-4AE6-B696-DEF7BF88EC13} - 18.4 + 18.5 FMX FMXClientAuthorization.dpr True @@ -159,7 +159,7 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png @@ -203,7 +203,7 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png @@ -244,7 +244,7 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png @@ -255,7 +255,7 @@ Debug DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXInterBaseDriver;emsclientfiredac;DataSnapFireDAC;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;soapserver;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FireDAC;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) true - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png @@ -292,13 +292,13 @@ 1033 true true - true false + PerMonitor Debug true - true + PerMonitor false @@ -308,11 +308,11 @@ true - true + PerMonitor true - true + PerMonitor @@ -353,12 +353,22 @@ true + + + true + + + + + true + + true - + true @@ -383,7 +393,6 @@ 1 - Contents\MacOS 0 @@ -393,6 +402,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -430,6 +445,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -508,6 +529,11 @@ 1 .framework + + Contents\MacOS + 1 + .framework + 0 @@ -530,6 +556,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .dll;.bpl @@ -553,6 +584,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .bpl @@ -575,6 +611,10 @@ Contents\Resources\StartUp\ 0 + + Contents\Resources\StartUp\ + 0 + 0 @@ -711,23 +751,41 @@ 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + ..\ 1 + + ..\ + 1 + Contents 1 + + Contents + 1 + Contents\Resources 1 + + Contents\Resources + 1 + @@ -750,6 +808,10 @@ Contents\MacOS 1 + + Contents\MacOS + 1 + 0 @@ -789,6 +851,7 @@ + diff --git a/Demos/Authorization/MARSAuthorization.dproj b/Demos/Authorization/MARSAuthorization.dproj index 4e76524a..edc60c8d 100755 --- a/Demos/Authorization/MARSAuthorization.dproj +++ b/Demos/Authorization/MARSAuthorization.dproj @@ -7,7 +7,7 @@ Application VCL DCC32 - 18.4 + 18.5 Win32 1 @@ -51,7 +51,7 @@ CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 1033 true - Web;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Soap;Winapi;$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace) + Web;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Soap;Winapi;$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace);$(DCC_NameSpace) false 00400000 false @@ -83,8 +83,8 @@ 0 - true true + PerMonitor DEBUG;$(DCC_Define) @@ -93,8 +93,8 @@ Debug - true true + PerMonitor diff --git a/Demos/Authorization/Server.Forms.Main.pas b/Demos/Authorization/Server.Forms.Main.pas index b393ec1e..c2978c43 100755 --- a/Demos/Authorization/Server.Forms.Main.pas +++ b/Demos/Authorization/Server.Forms.Main.pas @@ -67,7 +67,7 @@ implementation , MARS.Core.URL , MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyWriters , MARS.Core.MessageBodyReader, MARS.Core.MessageBodyReaders - , MARS.Utils.Parameters.IniFile + , MARS.Utils.Parameters.IniFile, MARS.Core.RequestAndResponse.Interfaces ; procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); @@ -86,10 +86,9 @@ procedure TMainForm.FormCreate(Sender: TObject); // skip favicon requests (browser) FEngine.BeforeHandleRequest := - function (const AEngine: TMARSEngine; const AURL: TMARSURL; - const ARequest: TWebRequest; const AResponse: TWebResponse; - var Handled: Boolean - ): Boolean + function(const AEngine: TMARSEngine; + const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; + var Handled: Boolean): Boolean begin Result := True; if SameText(AURL.Document, 'favicon.ico') then diff --git a/Demos/ContentTypes/Server.Resources.pas b/Demos/ContentTypes/Server.Resources.pas index 3181aad1..1b6dea09 100755 --- a/Demos/ContentTypes/Server.Resources.pas +++ b/Demos/ContentTypes/Server.Resources.pas @@ -77,10 +77,7 @@ THelloWorldResource = class function DataSet1: TDataSet; {$ifdef DelphiXE3_UP} - [GET, Path('/dataset2'), -// , Produces(TMediaType.APPLICATION_XML) -// , Produces(TMediaType.APPLICATION_JSON) -] + [GET, Path('/dataset2')] function DataSet2: TFDMemTable; [GET, Path('/dataset3') diff --git a/Demos/JsonDataObjects/JsonDataObjectsApacheModule.dproj b/Demos/JsonDataObjects/JsonDataObjectsApacheModule.dproj index 88a550c3..dc246b71 100644 --- a/Demos/JsonDataObjects/JsonDataObjectsApacheModule.dproj +++ b/Demos/JsonDataObjects/JsonDataObjectsApacheModule.dproj @@ -1,7 +1,7 @@  {9F324623-DE30-4882-85B1-E43C2F9E0A9F} - 18.4 + 18.5 VCL JsonDataObjectsApacheModule.dpr True @@ -134,7 +134,6 @@ 1 - Contents\MacOS 0 @@ -144,6 +143,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -180,6 +185,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -256,6 +267,10 @@ 1 .framework + + 1 + .framework + 0 @@ -265,6 +280,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -287,6 +306,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -308,6 +331,9 @@ 0 + + 0 + 0 @@ -418,6 +444,7 @@ 1 + @@ -425,6 +452,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -446,6 +477,9 @@ 1 + + 1 + 0 @@ -485,6 +519,7 @@ + diff --git a/Demos/JsonDataObjects/JsonDataObjectsConsole.dproj b/Demos/JsonDataObjects/JsonDataObjectsConsole.dproj index 297c0b8b..15bcddc4 100644 --- a/Demos/JsonDataObjects/JsonDataObjectsConsole.dproj +++ b/Demos/JsonDataObjects/JsonDataObjectsConsole.dproj @@ -1,7 +1,7 @@  {3989725D-F9E4-4178-9FBC-93DC284F63C7} - 18.4 + 18.5 VCL JsonDataObjectsConsole.dpr True @@ -130,7 +130,6 @@ 1 - Contents\MacOS 0 @@ -140,6 +139,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -176,6 +181,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -252,6 +263,10 @@ 1 .framework + + 1 + .framework + 0 @@ -261,6 +276,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -283,6 +302,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -304,6 +327,9 @@ 0 + + 0 + 0 @@ -414,6 +440,7 @@ 1 + @@ -421,6 +448,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -442,6 +473,9 @@ 1 + + 1 + 0 @@ -481,6 +515,7 @@ + diff --git a/Demos/JsonDataObjects/JsonDataObjectsFMX.dproj b/Demos/JsonDataObjects/JsonDataObjectsFMX.dproj index 235803dd..f7817217 100644 --- a/Demos/JsonDataObjects/JsonDataObjectsFMX.dproj +++ b/Demos/JsonDataObjects/JsonDataObjectsFMX.dproj @@ -1,7 +1,7 @@  {E0F4FA79-1B8C-437A-AB05-73EF7AD349A8} - 18.4 + 18.5 FMX JsonDataObjectsFMX.dpr True @@ -145,7 +145,7 @@ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png iPhoneAndiPad $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png @@ -189,7 +189,7 @@ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png iPhoneAndiPad $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png @@ -238,7 +238,7 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png @@ -260,7 +260,7 @@ /usr/X11/bin/xterm -e "%debuggee%" DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXInterBaseDriver;emsclientfiredac;DataSnapFireDAC;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;FireDACIBDriver;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;soapserver;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) true - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0 + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user Debug @@ -301,12 +301,12 @@ true 1033 true - true false + PerMonitor true - true + PerMonitor false @@ -316,11 +316,11 @@ true - true + PerMonitor true - true + PerMonitor @@ -365,15 +365,9 @@ true - - - true - - - - - Assets\ - Logo150x150.png + + + JsonDataObjectsFMX.exe true @@ -382,25 +376,27 @@ true - - - JsonDataObjectsFMX.exe + + + JsonDataObjectsFMX.icns true - + true - - + + + Info.plist true - + - JsonDataObjectsFMX.icns + Assets\ + Logo150x150.png true @@ -410,17 +406,31 @@ true - + - Info.plist true - + + + true + + + true + + + true + + + + + true + + Contents\MacOS\ @@ -437,7 +447,6 @@ 1 - Contents\MacOS 0 @@ -447,6 +456,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -483,6 +498,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -561,6 +582,11 @@ 1 .framework + + Contents\MacOS + 1 + .framework + 0 @@ -583,6 +609,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .dll;.bpl @@ -606,6 +637,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .bpl @@ -628,6 +664,10 @@ Contents\Resources\StartUp\ 0 + + Contents\Resources\StartUp\ + 0 + 0 @@ -764,23 +804,41 @@ 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + ..\ 1 + + ..\ + 1 + Contents 1 + + Contents + 1 + Contents\Resources 1 + + Contents\Resources + 1 + @@ -803,6 +861,10 @@ Contents\MacOS 1 + + Contents\MacOS + 1 + 0 @@ -842,6 +904,7 @@ + diff --git a/Demos/JsonDataObjects/JsonDataObjectsISAPI.dproj b/Demos/JsonDataObjects/JsonDataObjectsISAPI.dproj index f695c824..1b275ffd 100644 --- a/Demos/JsonDataObjects/JsonDataObjectsISAPI.dproj +++ b/Demos/JsonDataObjects/JsonDataObjectsISAPI.dproj @@ -1,7 +1,7 @@  {7F50F5E9-ED2D-4A28-9C9E-10FAA1B36C4D} - 18.4 + 18.5 VCL JsonDataObjectsISAPI.dpr True @@ -134,7 +134,6 @@ 1 - Contents\MacOS 0 @@ -144,6 +143,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -180,6 +185,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -256,6 +267,10 @@ 1 .framework + + 1 + .framework + 0 @@ -265,6 +280,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -287,6 +306,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -308,6 +331,9 @@ 0 + + 0 + 0 @@ -418,6 +444,7 @@ 1 + @@ -425,6 +452,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -446,6 +477,9 @@ 1 + + 1 + 0 @@ -485,6 +519,7 @@ + diff --git a/Demos/JsonDataObjects/JsonDataObjectsVCL.dproj b/Demos/JsonDataObjects/JsonDataObjectsVCL.dproj index 01c56804..77c18856 100644 --- a/Demos/JsonDataObjects/JsonDataObjectsVCL.dproj +++ b/Demos/JsonDataObjects/JsonDataObjectsVCL.dproj @@ -7,7 +7,7 @@ Application VCL DCC32 - 18.4 + 18.5 1 Win32 @@ -168,7 +168,6 @@ 1 - Contents\MacOS 0 @@ -178,6 +177,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -214,6 +219,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -290,6 +301,10 @@ 1 .framework + + 1 + .framework + 0 @@ -299,6 +314,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -321,6 +340,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -342,6 +365,9 @@ 0 + + 0 + 0 @@ -452,6 +478,7 @@ 1 + @@ -459,6 +486,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -480,6 +511,9 @@ 1 + + 1 + 0 @@ -519,6 +553,7 @@ + diff --git a/Demos/JsonDataObjects/JsonDataObjectsVCL.res b/Demos/JsonDataObjects/JsonDataObjectsVCL.res index da24bfd707ddf8ffc3b7ad123b2a0a4da2fdcaff..aff75aeb20d9f85f9fc0644b9444884261577bc7 100644 GIT binary patch delta 25 hcmbPmmbvFF^M;TIjMq1ZJ~+*^S?RG3^W=bM4gjK)3?2Xg delta 27 jcmeA<%RJ#M^M;TIj5jxjJ~+*^S?;kOGb6)f?`MtxwzCWy diff --git a/Demos/JsonDataObjects/JsonDataObjectsWinSvc.dproj b/Demos/JsonDataObjects/JsonDataObjectsWinSvc.dproj index a80b91d8..c84b3c58 100644 --- a/Demos/JsonDataObjects/JsonDataObjectsWinSvc.dproj +++ b/Demos/JsonDataObjects/JsonDataObjectsWinSvc.dproj @@ -1,7 +1,7 @@  {F72901B8-3DC2-48DB-9F80-EE7BA471758C} - 18.4 + 18.5 VCL JsonDataObjectsWinSvc.dpr True @@ -82,10 +82,10 @@ true - true 1033 true false + PerMonitor false @@ -94,8 +94,8 @@ 0 - true true + PerMonitor @@ -154,7 +154,6 @@ 1 - Contents\MacOS 0 @@ -164,6 +163,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -200,6 +205,12 @@ 1 + + + res\values-v21 + 1 + + res\drawable @@ -278,6 +289,11 @@ 1 .framework + + Contents\MacOS + 1 + .framework + 0 @@ -300,6 +316,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .dll;.bpl @@ -323,6 +344,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .bpl @@ -345,6 +371,10 @@ Contents\Resources\StartUp\ 0 + + Contents\Resources\StartUp\ + 0 + 0 @@ -481,23 +511,41 @@ 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + ..\ 1 + + ..\ + 1 + Contents 1 + + Contents + 1 + Contents\Resources 1 + + Contents\Resources + 1 + @@ -520,6 +568,10 @@ Contents\MacOS 1 + + Contents\MacOS + 1 + 0 @@ -559,6 +611,7 @@ + diff --git a/Demos/JsonDataObjects/Server.Resources.pas b/Demos/JsonDataObjects/Server.Resources.pas index 031a7b6d..ad0a2eda 100644 --- a/Demos/JsonDataObjects/Server.Resources.pas +++ b/Demos/JsonDataObjects/Server.Resources.pas @@ -10,15 +10,14 @@ interface uses SysUtils, Classes - , MARS.Core.Attributes - , MARS.Core.MediaType - , MARS.Core.Response +, MARS.Core.Attributes +, MARS.Core.MediaType +, MARS.Core.Response - , MARS.Core.Token.Resource +, MARS.Core.Token.Resource - , JsonDataObjects - , MARS.JsonDataObjects.ReadersAndWriters - ; +, JsonDataObjects, MARS.JsonDataObjects.ReadersAndWriters +; type [Path('helloworld')] diff --git a/Demos/MARSTemplate/MARSTemplateClient.dproj b/Demos/MARSTemplate/MARSTemplateClient.dproj index 583fbb0b..252a6005 100755 --- a/Demos/MARSTemplate/MARSTemplateClient.dproj +++ b/Demos/MARSTemplate/MARSTemplateClient.dproj @@ -4,10 +4,10 @@ MARSTemplateClient.dpr True Debug - 1169 + 33809 Application FMX - 18.5 + 19.0 Win32 @@ -18,8 +18,8 @@ Base true - - true + + true Base true @@ -33,13 +33,8 @@ Base true - - true - Base - true - - - true + + true Base true @@ -69,12 +64,6 @@ Base true - - true - Cfg_2 - true - true - true Cfg_2 @@ -87,8 +76,8 @@ true true - - true + + true Cfg_2 true true @@ -140,32 +129,40 @@ true android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png - - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera - iPhoneAndiPad - true + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= Debug - $(MSBuildProjectName) - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png @@ -175,6 +172,26 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera @@ -199,13 +216,28 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png - - /usr/bin/xterm -e "%debuggee%" - (None) - - - $(BDS)\bin\delphi_PROJECTICNS.icns + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + true + Base + true Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) @@ -240,17 +272,17 @@ false true - - true - Debug true - - $(BDS)\bin\delphi_PROJECTICNS.icns + + true + true + Cfg_2 + true true @@ -298,11 +330,10 @@ True - False + True True False - True - False + False True False @@ -336,11 +367,22 @@ true + + + true + + true + + + splash_image.png + true + + splash_image.png @@ -364,17 +406,6 @@ true - - - splash_image.png - true - - - - - true - - true @@ -452,12 +483,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -470,96 +509,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -648,6 +833,9 @@ 0 + + 0 + 0 @@ -680,6 +868,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -691,6 +890,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -702,6 +934,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -713,6 +1010,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -724,6 +1151,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -746,10 +1183,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -790,6 +1272,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + 1 @@ -842,6 +1334,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -866,6 +1362,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -903,6 +1405,7 @@ + 12 diff --git a/Demos/MARSTemplate/MARSTemplateServerApacheModule.dproj b/Demos/MARSTemplate/MARSTemplateServerApacheModule.dproj index a0905e5f..13717a78 100644 --- a/Demos/MARSTemplate/MARSTemplateServerApacheModule.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerApacheModule.dproj @@ -1,7 +1,7 @@  {0D982E91-6C92-4321-9078-458448B01536} - 18.5 + 19.0 None MARSTemplateServerApacheModule.dpr True @@ -189,12 +189,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -207,96 +215,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -366,6 +520,9 @@ 0 + + 0 + 0 @@ -396,6 +553,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -407,6 +575,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -418,6 +619,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -429,6 +695,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -440,6 +836,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -462,10 +868,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -480,6 +931,7 @@ + 1 @@ -509,6 +961,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -531,6 +987,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -568,6 +1030,7 @@ + True diff --git a/Demos/MARSTemplate/MARSTemplateServerApplication.dproj b/Demos/MARSTemplate/MARSTemplateServerApplication.dproj index 4e051b4d..c9ef6f5b 100644 --- a/Demos/MARSTemplate/MARSTemplateServerApplication.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerApplication.dproj @@ -7,7 +7,7 @@ 3 Application VCL - 18.5 + 19.0 Win32 diff --git a/Demos/MARSTemplate/MARSTemplateServerConsoleApplication.dproj b/Demos/MARSTemplate/MARSTemplateServerConsoleApplication.dproj index 4903d93a..33380dbd 100644 --- a/Demos/MARSTemplate/MARSTemplateServerConsoleApplication.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerConsoleApplication.dproj @@ -4,10 +4,10 @@ MARSTemplateServerConsoleApplication.dpr True Release - 133 + 4225 Console None - 18.5 + 19.0 Win32 @@ -18,8 +18,8 @@ Base true - - true + + true Base true @@ -33,8 +33,8 @@ Base true - - true + + true Base true @@ -70,12 +70,6 @@ Base true - - true - Cfg_2 - true - true - true Cfg_2 @@ -94,8 +88,8 @@ true true - - true + + true Cfg_2 true true @@ -145,25 +139,38 @@ true true android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png - - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera - iPhoneAndiPad - true + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= Debug - $(MSBuildProjectName) - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera @@ -187,9 +194,12 @@ (None) MARSTemplateServer_Icon.ico - - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationUsageDescription=The reason for accessing the location information of the user;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers Debug + true + Base + true /usr/X11/bin/xterm -e "%debuggee%" (None) @@ -224,9 +234,6 @@ false true - - true - Debug @@ -236,8 +243,11 @@ MARSTemplateServer_Icon.ico - + true + true + Cfg_2 + true 1033 @@ -283,11 +293,11 @@ False - False + False False False True - True + True True False @@ -355,12 +365,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -373,96 +391,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -532,6 +696,9 @@ 0 + + 0 + 0 @@ -562,6 +729,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -573,6 +751,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -584,6 +795,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -595,6 +871,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -606,6 +1012,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -628,10 +1044,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -646,6 +1107,7 @@ + 1 @@ -675,6 +1137,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -697,6 +1163,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -734,6 +1206,7 @@ + 12 diff --git a/Demos/MARSTemplate/MARSTemplateServerDaemon.dproj b/Demos/MARSTemplate/MARSTemplateServerDaemon.dproj index 27e6a8a7..522b0dba 100644 --- a/Demos/MARSTemplate/MARSTemplateServerDaemon.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerDaemon.dproj @@ -1,7 +1,7 @@  {9D225C2C-24C2-48B4-8ABE-52C04AF576AE} - 18.5 + 19.0 None MARSTemplateServerDaemon.dpr True @@ -18,8 +18,8 @@ Base true - - true + + true Base true @@ -38,8 +38,8 @@ Base true - - true + + true Base true @@ -99,9 +99,29 @@ $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png - - DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage);$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) @@ -115,8 +135,13 @@ MARSTemplateServer_Icon.ico (None) - - DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + Base + true + DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage);$(DCC_UsePackage) true @@ -244,12 +269,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -262,96 +295,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -440,6 +619,9 @@ 0 + + 0 + 0 @@ -472,6 +654,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -483,6 +676,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -494,6 +720,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -505,6 +796,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -516,6 +937,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -538,10 +969,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -582,6 +1058,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + 1 @@ -634,6 +1120,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -658,6 +1148,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -695,14 +1191,15 @@ + False - False + False False False True - False + False False False diff --git a/Demos/MARSTemplate/MARSTemplateServerFMXApplication.dproj b/Demos/MARSTemplate/MARSTemplateServerFMXApplication.dproj index f603e0be..5de257d5 100644 --- a/Demos/MARSTemplate/MARSTemplateServerFMXApplication.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerFMXApplication.dproj @@ -4,10 +4,10 @@ MARSTemplateServerFMXApplication.dpr True Release - 1173 + 37905 Application FMX - 18.5 + 19.0 Win32 @@ -18,8 +18,8 @@ Base true - - true + + true Base true @@ -33,13 +33,8 @@ Base true - - true - Base - true - - - true + + true Base true @@ -64,8 +59,8 @@ true true - - true + + true Cfg_1 true true @@ -81,12 +76,6 @@ Base true - - true - Cfg_2 - true - true - true Cfg_2 @@ -99,8 +88,8 @@ true true - - true + + true Cfg_2 true true @@ -152,32 +141,40 @@ true android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png - - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera - iPhoneAndiPad - true + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= Debug - $(MSBuildProjectName) - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png @@ -187,6 +184,26 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera @@ -211,16 +228,28 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png - - /usr/bin/xterm -e "%debuggee%" - (None) - MARSTemplateServer_Icon.ico - - - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers Debug true + true + Base + true /usr/X11/bin/xterm -e "%debuggee%" (None) MARSTemplateServer_Icon.ico @@ -253,9 +282,12 @@ Debug - + + true + Cfg_1 + true true - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers true @@ -270,18 +302,17 @@ false true - - true - Debug true - - $(BDS)\bin\delphi_PROJECTICNS.icns + true + true + Cfg_2 + true true @@ -327,11 +358,10 @@ True - False + True True False - True - True + True True False @@ -389,12 +419,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -407,96 +445,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -585,6 +769,9 @@ 0 + + 0 + 0 @@ -617,6 +804,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -628,6 +826,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -639,6 +870,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -650,6 +946,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -661,6 +1087,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -683,10 +1119,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -727,6 +1208,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + 1 @@ -779,6 +1270,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -803,6 +1298,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -840,6 +1341,7 @@ + 12 diff --git a/Demos/MARSTemplate/MARSTemplateServerISAPI.dproj b/Demos/MARSTemplate/MARSTemplateServerISAPI.dproj index 20690e27..25db71ca 100644 --- a/Demos/MARSTemplate/MARSTemplateServerISAPI.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerISAPI.dproj @@ -7,7 +7,7 @@ 1 Library None - 18.5 + 19.0 Win32 @@ -140,7 +140,6 @@ False False - False True False diff --git a/Demos/MARSTemplate/MARSTemplateServerService.dproj b/Demos/MARSTemplate/MARSTemplateServerService.dproj index 3a4cfef0..590f7458 100644 --- a/Demos/MARSTemplate/MARSTemplateServerService.dproj +++ b/Demos/MARSTemplate/MARSTemplateServerService.dproj @@ -7,7 +7,7 @@ 3 Application None - 18.5 + 19.0 Win32 @@ -192,7 +192,6 @@ False False - False True True @@ -272,12 +271,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -290,96 +297,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -468,6 +621,9 @@ 0 + + 0 + 0 @@ -500,6 +656,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -511,6 +678,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -522,6 +722,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -533,6 +798,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -544,6 +939,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -566,10 +971,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -610,6 +1060,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + 1 @@ -662,6 +1122,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -686,6 +1150,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -723,6 +1193,7 @@ + 12 diff --git a/Demos/MARSTemplate/Server.Ignition.pas b/Demos/MARSTemplate/Server.Ignition.pas index 93fbdc6c..434b319e 100644 --- a/Demos/MARSTemplate/Server.Ignition.pas +++ b/Demos/MARSTemplate/Server.Ignition.pas @@ -10,9 +10,12 @@ interface uses - Classes, SysUtils, Rtti - , MARS.Core.Engine -; + System.Classes, + System.SysUtils, + System.RTTI, + System.ZLib, + System.StrUtils, + MARS.Core.Engine; type TServerEngine=class @@ -94,15 +97,28 @@ implementation *) {$ENDREGION} {$REGION 'Global AfterInvoke handler example'} -(* - // to execute something after each activation - TMARSActivation.RegisterAfterInvoke( - procedure (const AActivation: IMARSActivation) - begin - - end - ); -*) + // Compression + if FEngine.Parameters.ByName('Compression.Enabled').AsBoolean then + TMARSActivation.RegisterAfterInvoke( + procedure (const AActivation: IMARSActivation) + var + LOutputStream: TBytesStream; + begin + if ContainsText(AActivation.Request.GetHeaderParamValue('Accept-Encoding'), 'gzip') then + begin + LOutputStream := TBytesStream.Create(nil); + try + ZipStream(AActivation.Response.ContentStream, LOutputStream, 15 + 16); + AActivation.Response.ContentStream.Free; + AActivation.Response.ContentStream := LOutputStream; + AActivation.Response.ContentEncoding := 'gzip'; + except + LOutputStream.Free; + raise; + end; + end; + end + ); {$ENDREGION} {$REGION 'Global InvokeError handler example'} (* diff --git a/Demos/MARSTemplate/Server.Service.pas b/Demos/MARSTemplate/Server.Service.pas index f3882f4d..da27291a 100644 --- a/Demos/MARSTemplate/Server.Service.pas +++ b/Demos/MARSTemplate/Server.Service.pas @@ -79,6 +79,9 @@ procedure TServerService.ServiceCreate(Sender: TObject); var LScheduler: TIdSchedulerOfThreadPool; begin + Name := TServerEngine.Default.Parameters.ByNameText('ServiceName', Name).AsString; + DisplayName := TServerEngine.Default.Parameters.ByNameText('ServiceDisplayName', DisplayName).AsString; + if WebRequestHandler <> nil then WebRequestHandler.WebModuleClass := WebModuleClass; diff --git a/Demos/MARSTemplate/Server.WebModule.pas b/Demos/MARSTemplate/Server.WebModule.pas index db2377d5..72908095 100644 --- a/Demos/MARSTemplate/Server.WebModule.pas +++ b/Demos/MARSTemplate/Server.WebModule.pas @@ -31,14 +31,15 @@ implementation {$R *.dfm} uses - Server.Ignition; + MARS.http.Server.Indy +, Server.Ignition; procedure TServerWebModule.ServerWebModuleDefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin inherited; - if not TServerEngine.Default.HandleRequest(Request, Response) then + if not TServerEngine.Default.HandleRequest(TMARSWebRequest.Create(Request), TMARSWebResponse.Create(Response)) then begin Response.ContentType := 'application/json'; Response.Content := diff --git a/Demos/MARSTemplate/bin/MARSTemplateServerApplication.ini b/Demos/MARSTemplate/bin/MARSTemplateServerApplication.ini index e098e5da..4398fa63 100644 --- a/Demos/MARSTemplate/bin/MARSTemplateServerApplication.ini +++ b/Demos/MARSTemplate/bin/MARSTemplateServerApplication.ini @@ -1,2 +1,3 @@ [DefaultEngine] -ThreadPoolSize=100 \ No newline at end of file +ThreadPoolSize=100 +;Compression.Enabled=True diff --git a/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.dfm b/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.dfm new file mode 100644 index 00000000..97328db3 --- /dev/null +++ b/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.dfm @@ -0,0 +1,25 @@ +object MainDataModule: TMainDataModule + OldCreateOrder = False + Height = 411 + Width = 518 + object MARSApplication: TMARSClientApplication + DefaultMediaType = 'application/json' + DefaultContentType = 'application/json' + Client = MARSClient + Left = 88 + Top = 80 + end + object MARSClient: TMARSNetClient + MARSEngineURL = 'http://localhost:8080/rest' + ConnectTimeout = 60000 + ReadTimeout = 60000 + HttpClient.Asynchronous = False + HttpClient.ConnectionTimeout = 60000 + HttpClient.ResponseTimeout = 60000 + HttpClient.AllowCookies = True + HttpClient.HandleRedirects = True + HttpClient.UserAgent = 'Embarcadero URI Client/1.0' + Left = 88 + Top = 24 + end +end diff --git a/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.pas b/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.pas new file mode 100644 index 00000000..4b24449c --- /dev/null +++ b/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.pas @@ -0,0 +1,32 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit FMXClient.DataModules.Main; + +interface + +uses + System.SysUtils, System.Classes, MARS.Client.Application, + MARS.Client.Client, MARS.Client.Client.Net +; + +type + TMainDataModule = class(TDataModule) + MARSApplication: TMARSClientApplication; + MARSClient: TMARSNetClient; + private + public + end; + +var + MainDataModule: TMainDataModule; + +implementation + +{%CLASSGROUP 'FMX.Controls.TControl'} + +{$R *.dfm} + +end. diff --git a/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.vlb b/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.vlb new file mode 100644 index 00000000..95e7a6a2 --- /dev/null +++ b/Demos/MARSTemplateDCS/FMXClient.DataModules.Main.vlb @@ -0,0 +1,10 @@ +[MARSApplication] +Coordinates=150,53,96,33 + +[] +Coordinates=71,70,69,33 +Visible=False + +[MainForm.BindSourceDB1] +Coordinates=0,0,144,267 + diff --git a/Demos/MARSTemplateDCS/FMXClient.Forms.Main.fmx b/Demos/MARSTemplateDCS/FMXClient.Forms.Main.fmx new file mode 100644 index 00000000..9990bf0e --- /dev/null +++ b/Demos/MARSTemplateDCS/FMXClient.Forms.Main.fmx @@ -0,0 +1,27 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'MARS Template Client' + ClientHeight = 480 + ClientWidth = 640 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + DesignerMasterStyle = 0 + object TopToolBar: TToolBar + Size.Width = 640.000000000000000000 + Size.Height = 48.000000000000000000 + Size.PlatformDefault = False + TabOrder = 0 + object TitleLabel: TLabel + Align = Center + AutoSize = True + Size.Width = 121.000000000000000000 + Size.Height = 16.000000000000000000 + Size.PlatformDefault = False + StyleLookup = 'toollabel' + TextSettings.WordWrap = False + Text = 'MARS Template Client' + end + end +end diff --git a/Demos/MARSTemplateDCS/FMXClient.Forms.Main.pas b/Demos/MARSTemplateDCS/FMXClient.Forms.Main.pas new file mode 100644 index 00000000..e56b7400 --- /dev/null +++ b/Demos/MARSTemplateDCS/FMXClient.Forms.Main.pas @@ -0,0 +1,34 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit FMXClient.Forms.Main; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Layouts, FMX.Controls.Presentation; + +type + TMainForm = class(TForm) + TopToolBar: TToolBar; + TitleLabel: TLabel; + private + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.fmx} + +uses + FMXClient.DataModules.Main + ; + +end. diff --git a/Demos/MARSTemplateDCS/FMXClient.Forms.Main.vlb b/Demos/MARSTemplateDCS/FMXClient.Forms.Main.vlb new file mode 100644 index 00000000..b439f67e --- /dev/null +++ b/Demos/MARSTemplateDCS/FMXClient.Forms.Main.vlb @@ -0,0 +1,26 @@ +[TopToolBar] +Coordinates=317,78,82,36 + +[MainDataModule.ItemsQueryDataSet] +Coordinates=10,10,228,212 + +[] +Coordinates=145,78,71,36 +Visible=True + +[TitleLabel] +Coordinates=236,78,71,58 + +[MainDataModule.] +Coordinates=472,428,215,249 +Visible=False + +[MainDataModule.EmployeeQuery1] +Visible=False + +[MainDataModule.EmployeeQueryDataSet] +Coordinates=100,10,253,58 + +[MainDataModule.CountryByName1] +Coordinates=10,78,216,58 + diff --git a/Demos/MARSTemplateDCS/MARSTemplateClientDCS.dpr b/Demos/MARSTemplateDCS/MARSTemplateClientDCS.dpr new file mode 100644 index 00000000..55e764b3 --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateClientDCS.dpr @@ -0,0 +1,21 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) + program MARSTemplateClientDCS; + +uses + System.StartUpCopy, + FMX.Forms, + FMXClient.Forms.Main in 'FMXClient.Forms.Main.pas' {MainForm}, + FMXClient.DataModules.Main in 'FMXClient.DataModules.Main.pas' {MainDataModule: TDataModule}; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainDataModule, MainDataModule); + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/MARSTemplateDCS/MARSTemplateClientDCS.dproj b/Demos/MARSTemplateDCS/MARSTemplateClientDCS.dproj new file mode 100644 index 00000000..d2bb36e1 --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateClientDCS.dproj @@ -0,0 +1,1423 @@ + + + {B6B860BA-E3A5-48E9-8EC6-7FE516AE7FC4} + MARSTemplateClientDCS.dpr + True + Debug + 33809 + Application + FMX + 19.0 + Win32 + + + true + + + true + Base + true + + + true + Base + 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 + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + MARSTemplateClientDCS + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + .\bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + true + Base + true + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + MARSTemplateServer_Icon.ico + + + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + PerMonitor + MARSTemplateServer_Icon.ico + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + + + true + true + Cfg_2 + true + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + MARSTemplateServer_Icon.ico + PerMonitor + + + + MainSource + + +
MainForm
+
+ +
MainDataModule
+ TDataModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + MARSTemplateClientDCS.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + True + True + False + False + True + False + + + + + true + + + + + ic_launcher.png + true + + + + + MARSTemplateClientDCS.exe + true + + + + + ic_launcher.png + true + + + + + libMARSTemplateClient.so + true + + + + + ic_launcher.png + true + + + + + true + + + + + true + + + + + splash_image.png + true + + + + + true + + + + + splash_image.png + true + + + + + splash_image.png + true + + + + + splash_image.png + true + + + + + true + + + + + libMARSTemplateClientDCS.so + true + + + + + true + + + + + classes.dex + true + + + + + ic_launcher.png + true + + + + + ic_launcher.png + true + + + + + true + + + + + true + + + + + libMARSTemplateClient.so + true + + + + + true + + + + + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/MARSTemplateDCS/MARSTemplateDCSProjectGroup.groupproj b/Demos/MARSTemplateDCS/MARSTemplateDCSProjectGroup.groupproj new file mode 100644 index 00000000..6f92b123 --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateDCSProjectGroup.groupproj @@ -0,0 +1,96 @@ + + + {6E23DEFF-F737-42C3-B8AD-2549B8F67C93} + + + + + + + + + + + + + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dpr b/Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dpr new file mode 100644 index 00000000..607e67af --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dpr @@ -0,0 +1,16 @@ +program MARSTemplateServerDCSApplication; + +uses + Vcl.Forms, + Server.DCS.Forms.Main in 'Server.DCS.Forms.Main.pas' {MainForm}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dproj b/Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dproj new file mode 100644 index 00000000..dfd71c9a --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSApplication.dproj @@ -0,0 +1,1133 @@ + + + {044D26EF-293D-4A96-9B9B-2E6CF9811486} + 19.0 + VCL + MARSTemplateServerDCSApplication.dpr + True + Release + Win64 + 3 + Application + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + .\$(Platform)\$(Config) + .\bin + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + MARSTemplateServerDCSApplication + 1040 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + DBXSqliteDriver;RESTComponents;fmxase;DBXDb2Driver;DBXInterBaseDriver;MARSClient.FireDAC;vclactnband;vclFireDAC;emsclientfiredac;tethering;svnui;DataSnapFireDAC;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;FireDACIBDriver;fmxdae;FormatFloatPackage;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;emsclient;DataSnapCommon;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;CloudService;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;IndyIPCommon;bindcompdbx;vcl;IndyIPServer;DBXSybaseASEDriver;DataSetRESTRequestAdapterPackage;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;emsserverresource;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;MARSClient.Core;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + 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 + $(BDS)\bin\default_app.manifest + + + DBXSqliteDriver;RESTComponents;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;emsclientfiredac;tethering;DataSnapFireDAC;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;FireDACIBDriver;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;emsclient;DataSnapCommon;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;CloudService;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;IndyIPCommon;bindcompdbx;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;emsserverresource;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;MARSClient.Core;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + $(BDS)\bin\default_app.manifest + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + true + PerMonitorV2 + + + true + PerMonitorV2 + true + 1033 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + PerMonitorV2 + + + true + PerMonitorV2 + + + + MainSource + + +
MainForm
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Application + + + + MARSTemplateServerDCSApplication.dpr + + + DBExpress Enterprise Data Explorer Integration + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + CodeSite Express 5.3.3 + + + + + + MARSTemplateServerDCSApplication.exe + true + + + + + MARSTemplateServerDCSApplication.exe + true + + + + + MARSTemplateServerDCSApplication.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + + + 12 + + + + +
diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dpr b/Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dpr new file mode 100644 index 00000000..2ed845df --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dpr @@ -0,0 +1,201 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program MARSTemplateServerDCSConsoleApplication; +{$APPTYPE CONSOLE} + +{$I MARS.inc} + +uses +{$ifdef DelphiXE3_UP} + System.SysUtils, + System.Types, +// IPPeerServer, IPPeerAPI, + IdHTTPWebBrokerBridge, + IdSchedulerOfThreadPool, + Web.WebReq, + Web.WebBroker, +{$else} + SysUtils, StrUtils, + Types, + IdHTTPWebBrokerBridge, + IdSchedulerOfThreadPool, + WebReq, + WebBroker, +{$endif} + IdContext, + ServerConst in 'ServerConst.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}, + Server.Ignition in 'Server.Ignition.pas'; + +{$R *.res} + +type + TDummyIndyServer = class + public + procedure ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); virtual; + end; + + +procedure StartServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if not (AServer.Active) then + begin + AServer.DefaultPort := TServerEngine.Default.Port; + Writeln(Format(sStartingServer, [AServer.DefaultPort])); + AServer.Active := True; + end + else + Writeln(sServerRunning); + Write(cArrow); +end; + +procedure StopServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if AServer.Active then + begin + Writeln(sStoppingServer); + AServer.Active := False; + Writeln(sServerStopped); + end + else + Writeln(sServerNotRunning); + Write(cArrow); +end; + +procedure SetPort(const AServer: TIdHTTPWebBrokerBridge; const APort: string); +var + LPort: Integer; + LWasActive: Boolean; +begin + LPort := StrToIntDef(APort, -1); + if LPort = -1 then + begin + Writeln('Port should be an integer number. Try again.'); + Exit; + end; + + LWasActive := AServer.Active; + if LWasActive then + StopServer(AServer); + TServerEngine.Default.Port := LPort; + if LWasActive then + StartServer(AServer); + Writeln(Format(sPortSet, [IntToStr(TServerEngine.Default.Port)])); + Write(cArrow); +end; + +procedure WriteCommands; +begin + Writeln(sCommands); + Write(cArrow); +end; + +procedure WriteStatus(const AServer: TIdHTTPWebBrokerBridge); +begin + Writeln(sIndyVersion + AServer.SessionList.Version); + Writeln(sActive + BoolToStr(AServer.Active, True)); + Writeln(sPort + IntToStr(TServerEngine.Default.Port)); + Write(cArrow); +end; + +procedure SetupThreadScheduler(const AServer: TIdHTTPWebBrokerBridge); +var + LScheduler: TIdSchedulerOfThreadPool; +begin + LScheduler := TIdSchedulerOfThreadPool.Create(AServer); + try + LScheduler.PoolSize := TServerEngine.Default.ThreadPoolSize; + AServer.Scheduler := LScheduler; + AServer.MaxConnections := LScheduler.PoolSize; + except + AServer.Scheduler.DisposeOf; + AServer.Scheduler := nil; + raise; + end; +end; + +procedure RunServer(); +var + LServer: TIdHTTPWebBrokerBridge; + LDummyIndy: TDummyIndyServer; + LResponse: string; +begin + WriteCommands; + LDummyIndy := TDummyIndyServer.Create; + try + LServer := TIdHTTPWebBrokerBridge.Create(nil); + try + LServer.DefaultPort := TServerEngine.Default.Port; + LServer.OnParseAuthentication := LDummyIndy.ParseAuthenticationHandler; + {$IFNDEF LINUX} + SetupThreadScheduler(LServer); + {$ENDIF} + + while True do + begin + Readln(LResponse); + LResponse := LowerCase(LResponse); + if sametext(LResponse, cCommandStart) then + StartServer(LServer) + else if sametext(LResponse, cCommandStatus) then + WriteStatus(LServer) + else if sametext(LResponse, cCommandStop) then + StopServer(LServer) + {$ifdef DelphiXE3_UP} + else if LResponse.StartsWith(cCommandSetPort, True) then + SetPort(LServer, LResponse.Split([' '])[2]) + {$else} + else if AnsiStartsText(cCommandSetPort, LResponse) then + SetPort(LServer, Copy(LResponse, Length(cCommandSetPort)+1, MAXINT)) + {$endif} + + else if sametext(LResponse, cCommandHelp) then + WriteCommands + else if sametext(LResponse, cCommandExit) then + if LServer.Active then + begin + StopServer(LServer); + break + end + else + break + else + begin + Writeln(sInvalidCommand); + Write(cArrow); + end; + end; + finally + LServer.Free; + end; + finally + LDummyIndy.Free; + end; +end; + +{ TDummyIndyServer } + +procedure TDummyIndyServer.ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); +begin + // Allow JWT Bearer authentication's scheme + if SameText(AAuthType, 'Bearer') then + VHandled := True; +end; + +begin + try + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + RunServer(); + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end +end. diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dproj b/Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dproj new file mode 100644 index 00000000..624418ce --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSConsoleApplication.dproj @@ -0,0 +1,1224 @@ + + + {8EEC8DF2-3740-468A-937A-60168245FB71} + MARSTemplateServerDCSConsoleApplication.dpr + True + Release + 4225 + Console + None + 19.0 + Win32 + + + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + MARSTemplateServerDCSConsoleApplication + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + ./bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + MARSTemplateServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationUsageDescription=The reason for accessing the location information of the user;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + Debug + true + Base + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + + + 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=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + MARSTemplateServer_Icon.ico + (None) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + MARSTemplateServerConsoleApplication_Icon.ico + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + MARSTemplateServer_Icon.ico + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + MARSTemplateServer_Icon.ico + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + + + MARSTemplateServer_Icon.ico + + + true + true + Cfg_2 + true + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + MARSTemplateServer_Icon.ico + + + + MainSource + + + +
ServerWebModule
+ TWebModule +
+ + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + MARSTemplateServerDCSConsoleApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + False + True + True + True + False + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + MARSTemplateServerDCSConsoleApplication + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARSTemplateServerDCSConsoleApplication.exe + true + + + + + Assets\ + Logo44x44.png + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dpr b/Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dpr new file mode 100644 index 00000000..7e459dd8 --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dpr @@ -0,0 +1,19 @@ +program MARSTemplateServerDCSDaemon; + +{$APPTYPE CONSOLE} + +{$R *.res} + +uses + Classes, + SysUtils, + {$ENDIF } + Server.Ignition in 'Server.Ignition.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +begin + {$IFDEF LINUX} + TMARSDaemon.Current.Name := 'MARSTemplateServerDaemon'; + TMARSDaemon.Current.Start; + {$ENDIF} +end. diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dproj b/Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dproj new file mode 100644 index 00000000..7bea564e --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSDaemon.dproj @@ -0,0 +1,1211 @@ + + + {9D225C2C-24C2-48B4-8ABE-52C04AF576AE} + 19.0 + None + MARSTemplateServerDCSDaemon.dpr + True + Debug + Linux64 + 128 + Console + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Base + true + + + .\lib\$(Platform)\$(Config) + .\bin + false + false + false + false + false + RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage) + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + MARSTemplateServerDCSDaemon + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage);$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DataSnapServerMidas;FireDACADSDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;inetdb;emsedge;dbexpress;IndyCore;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;bindengine;FireDACOracleDriver;FireDACMySQLDriver;FireDACCommonODBC;DataSnapClient;IndySystem;FireDACDb2Driver;FireDACInfxDriver;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;rtl;DbxClientDriver;CustomIPTransport;bindcomp;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;dbrtl;FireDACMongoDBDriver;IndyProtocols;$(DCC_UsePackage) + /usr/bin/xterm -e "%debuggee%" + MARSTemplateServer_Icon.ico + (None) + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + Base + true + DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage);$(DCC_UsePackage) + true + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;CodeSiteExpressPkg;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;RadiantShapesFmx_Design;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(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 + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + /usr/bin/xterm -e "%debuggee%" + + + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Application + + + + MARSTemplateServerDCSDaemon.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARSTemplateServerDCSDaemon + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + False + False + False + False + True + False + False + False + + + 12 + + + + +
diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dpr b/Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dpr new file mode 100644 index 00000000..332b72ad --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dpr @@ -0,0 +1,21 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program MARSTemplateServerDCSFMXApplication; + +uses + System.StartUpCopy, + FMX.Forms, + Server.FMX.Forms.Main in 'Server.FMX.Forms.Main.pas' {MainForm}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dproj b/Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dproj new file mode 100644 index 00000000..0785c5bb --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSFMXApplication.dproj @@ -0,0 +1,1354 @@ + + + {2DC28130-9EB1-48C8-9CA6-0F3CFD5D8E1D} + MARSTemplateServerDCSFMXApplication.dpr + True + Release + 37905 + Application + FMX + 19.0 + Win32 + + + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + MARSTemplateServerDCSFMXApplication + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + .\bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + Debug + true + true + Base + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + MARSTemplateServer_Icon.ico + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + MARSTemplateServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + MARSTemplateServerFMXApplication_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + Debug + + + true + Cfg_1 + true + true + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + MARSTemplateServer_Icon.ico + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + MARSTemplateServer_Icon.ico + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + + + true + true + Cfg_2 + true + MARSTemplateServer_Icon.ico + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + MARSTemplateServer_Icon.ico + PerMonitor + + + + MainSource + + +
MainForm
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + MARSTemplateServerDCSFMXApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + True + True + False + True + True + False + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARSTemplateServerDCSFMXApplication.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dpr b/Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dpr new file mode 100644 index 00000000..87b5702d --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dpr @@ -0,0 +1,43 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program MARSTemplateServerDCSService; + +{$I MARS.inc} + +uses +{$ifdef DelphiXE3_UP} + Vcl.SvcMgr, +{$else} + SvcMgr, +{$endif} + + Server.Service in 'Server.Service.pas' {ServerService: TService}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +{$R *.RES} + +begin + // Windows 2003 Server requires StartServiceCtrlDispatcher to be + // called before CoRegisterClassObject, which can be called indirectly + // by Application.Initialize. TServiceApplication.DelayInitialize allows + // Application.Initialize to be called from TService.Main (after + // StartServiceCtrlDispatcher has been called). + // + // Delayed initialization of the Application object may affect + // events which then occur prior to initialization, such as + // TService.OnCreate. It is only recommended if the ServiceApplication + // registers a class object with OLE and is intended for use with + // Windows 2003 Server. + // + // Application.DelayInitialize := True; + // + if not Application.DelayInitialize or Application.Installing then + Application.Initialize; + Application.CreateForm(TServerService, ServerService); + Application.Run; +end. diff --git a/Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dproj b/Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dproj new file mode 100644 index 00000000..6eb3a0c6 --- /dev/null +++ b/Demos/MARSTemplateDCS/MARSTemplateServerDCSService.dproj @@ -0,0 +1,1210 @@ + + + {FEF5DF94-50B0-46F7-9F4B-6C355F140A2F} + MARSTemplateServerDCSService.dpr + True + Release + 3 + Application + None + 19.0 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + MARSTemplateServerDCSService + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;Vcl;Winapi;$(DCC_Namespace) + .\lib\$(Platform)\$(Config) + .\bin + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + MARSTemplateServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + MARSTemplateServer_Icon.ico + true + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + MARS.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + + MainSource + + +
ServerService
+ TService +
+ + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + MARSTemplateServerDCSService.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + True + True + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + Assets\ + Logo44x44.png + true + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARSTemplateServerDCSService.exe + true + + + + + Assets\ + Logo44x44.png + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/MARSTemplateDCS/MARSTemplateServer_Icon.ico b/Demos/MARSTemplateDCS/MARSTemplateServer_Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0067d86ba25beefed52ee5181c1b8865b4e6c8ba GIT binary patch literal 67646 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 0 then + ATreeView.Items[0].Expand(True); + finally + ATreeView.Items.EndUpdate; + end; + +end; + +procedure TMainForm.StartServerActionExecute(Sender: TObject); +begin + // http server implementation + FServer := TMARShttpServerDCS.Create(TServerEngine.Default); + try + FServer.DefaultPort := TServerEngine.Default.Port; + FServer.Active := True; + except + FServer.Free; + raise; + end; +end; + +procedure TMainForm.StartServerActionUpdate(Sender: TObject); +begin + StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); +end; + +procedure TMainForm.StopServerActionExecute(Sender: TObject); +begin + FServer.Active := False; + FreeAndNil(FServer); +end; + +procedure TMainForm.StopServerActionUpdate(Sender: TObject); +begin + StopServerAction.Enabled := Assigned(FServer) and (FServer.Active = True); +end; + +end. diff --git a/Demos/MARSTemplateDCS/Server.FMX.Forms.Main.fmx b/Demos/MARSTemplateDCS/Server.FMX.Forms.Main.fmx new file mode 100644 index 00000000..fdadb145 --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.FMX.Forms.Main.fmx @@ -0,0 +1,66 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'MARS-Curiosity Template Server FMX' + ClientHeight = 480 + ClientWidth = 640 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnClose = FormClose + DesignerMasterStyle = 0 + object Layout1: TLayout + Align = Top + Size.Width = 640.000000000000000000 + Size.Height = 121.000000000000000000 + Size.PlatformDefault = False + TabOrder = 1 + object PortNumberEdit: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 0 + Text = '8080' + Position.X = 109.000000000000000000 + Position.Y = 22.000000000000000000 + OnChange = PortNumberEditChange + end + object Label1: TLabel + Position.X = 16.000000000000000000 + Position.Y = 24.000000000000000000 + Size.Width = 89.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = 'Port number:' + end + object StartButton: TButton + Action = StartServerAction + Enabled = True + ImageIndex = -1 + Position.X = 16.000000000000000000 + Position.Y = 64.000000000000000000 + TabOrder = 2 + end + object StopButton: TButton + Action = StopServerAction + Enabled = True + ImageIndex = -1 + Position.X = 104.000000000000000000 + Position.Y = 64.000000000000000000 + TabOrder = 3 + end + end + object MainActionList: TActionList + Left = 384 + Top = 24 + object StartServerAction: TAction + Text = 'Start Server' + OnExecute = StartServerActionExecute + OnUpdate = StartServerActionUpdate + end + object StopServerAction: TAction + Text = 'Stop Server' + OnExecute = StopServerActionExecute + OnUpdate = StopServerActionUpdate + end + end +end diff --git a/Demos/MARSTemplateDCS/Server.FMX.Forms.Main.pas b/Demos/MARSTemplateDCS/Server.FMX.Forms.Main.pas new file mode 100644 index 00000000..40aea6cc --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.FMX.Forms.Main.pas @@ -0,0 +1,97 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.FMX.Forms.Main; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Controls.Presentation, FMX.Edit, FMX.Layouts, System.Actions, FMX.ActnList +, MARS.http.Server.DCS +; + +type + TMainForm = class(TForm) + MainActionList: TActionList; + StartServerAction: TAction; + StopServerAction: TAction; + Layout1: TLayout; + PortNumberEdit: TEdit; + Label1: TLabel; + StartButton: TButton; + StopButton: TButton; + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure FormCreate(Sender: TObject); + procedure StartServerActionExecute(Sender: TObject); + procedure StopServerActionExecute(Sender: TObject); + procedure StartServerActionUpdate(Sender: TObject); + procedure StopServerActionUpdate(Sender: TObject); + procedure PortNumberEditChange(Sender: TObject); + private + FServer: TMARShttpServerDCS; + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.fmx} + +uses + MARS.Core.URL, MARS.Core.Engine +, Server.Ignition +; + +procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); +begin + StopServerAction.Execute; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + PortNumberEdit.Text := TServerEngine.Default.Port.ToString; + + StartServerAction.Execute; +end; + +procedure TMainForm.PortNumberEditChange(Sender: TObject); +begin + TServerEngine.Default.Port := StrToInt(PortNumberEdit.Text); +end; + +procedure TMainForm.StartServerActionExecute(Sender: TObject); +begin + // http server implementation + FServer := TMARShttpServerDCS.Create(TServerEngine.Default); + try + FServer.DefaultPort := TServerEngine.Default.Port; + FServer.Active := True; + except + FServer.Free; + raise; + end; +end; + +procedure TMainForm.StartServerActionUpdate(Sender: TObject); +begin + StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); +end; + +procedure TMainForm.StopServerActionExecute(Sender: TObject); +begin + FServer.Active := False; + FreeAndNil(FServer); +end; + +procedure TMainForm.StopServerActionUpdate(Sender: TObject); +begin + StopServerAction.Enabled := Assigned(FServer) and (FServer.Active = True); +end; + +end. diff --git a/Demos/MARSTemplateDCS/Server.Forms.Main.dfm b/Demos/MARSTemplateDCS/Server.Forms.Main.dfm new file mode 100644 index 00000000..4bd5c813 --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.Forms.Main.dfm @@ -0,0 +1,83 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'MARSTemplate Server' + ClientHeight = 201 + ClientWidth = 464 + Color = clBtnFace + Constraints.MinHeight = 240 + Constraints.MinWidth = 480 + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + OnClose = FormClose + OnCreate = FormCreate + PixelsPerInch = 96 + TextHeight = 13 + object TopPanel: TPanel + Left = 0 + Top = 0 + Width = 464 + Height = 73 + Align = alTop + BevelOuter = bvNone + TabOrder = 0 + object Label1: TLabel + Left = 28 + Top = 17 + Width = 63 + Height = 13 + Caption = 'Port number:' + end + object StartButton: TButton + Left = 16 + Top = 41 + Width = 75 + Height = 25 + Action = StartServerAction + TabOrder = 0 + end + object StopButton: TButton + Left = 104 + Top = 41 + Width = 75 + Height = 25 + Action = StopServerAction + TabOrder = 1 + end + object PortNumberEdit: TEdit + Left = 97 + Top = 14 + Width = 82 + Height = 21 + TabOrder = 2 + OnChange = PortNumberEditChange + end + end + object MainTreeView: TTreeView + Left = 0 + Top = 73 + Width = 464 + Height = 128 + Align = alClient + Indent = 19 + TabOrder = 1 + end + object MainActionList: TActionList + Left = 384 + Top = 24 + object StartServerAction: TAction + Caption = 'Start Server' + OnExecute = StartServerActionExecute + OnUpdate = StartServerActionUpdate + end + object StopServerAction: TAction + Caption = 'Stop Server' + OnExecute = StopServerActionExecute + OnUpdate = StopServerActionUpdate + end + end +end diff --git a/Demos/MARSTemplateDCS/Server.Forms.Main.pas b/Demos/MARSTemplateDCS/Server.Forms.Main.pas new file mode 100644 index 00000000..0673418a --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.Forms.Main.pas @@ -0,0 +1,148 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Forms.Main; + +{$I MARS.inc} + +interface + +uses Classes, SysUtils, Forms, ActnList, ComCtrls, StdCtrls, Controls, ExtCtrls + , System.Actions + , MARS.http.Server.Indy +; + +type + TMainForm = class(TForm) + MainActionList: TActionList; + StartServerAction: TAction; + StopServerAction: TAction; + TopPanel: TPanel; + Label1: TLabel; + StartButton: TButton; + StopButton: TButton; + PortNumberEdit: TEdit; + MainTreeView: TTreeView; + procedure StartServerActionExecute(Sender: TObject); + procedure StartServerActionUpdate(Sender: TObject); + procedure StopServerActionExecute(Sender: TObject); + procedure StopServerActionUpdate(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure PortNumberEditChange(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + private + FServer: TMARShttpServerIndy; + protected + procedure RenderEngines(const ATreeView: TTreeView); + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.dfm} + +uses + StrUtils, Web.HttpApp + , MARS.Core.URL, MARS.Core.Engine, MARS.Core.Application, MARS.Core.Registry + , MARS.Core.Registry.Utils + , Server.Ignition +; + +procedure TMainForm.RenderEngines(const ATreeView: TTreeView); +begin + + ATreeview.Items.BeginUpdate; + try + ATreeview.Items.Clear; + TMARSEngineRegistry.Instance.EnumerateEngines( + procedure (AName: string; AEngine: TMARSEngine) + var + LEngineItem: TTreeNode; + begin + LEngineItem := ATreeview.Items.AddChild(nil + , AName + ' [ :' + AEngine.Port.ToString + AEngine.BasePath + ']' + ); + + AEngine.EnumerateApplications( + procedure (AName: string; AApplication: TMARSApplication) + var + LApplicationItem: TTreeNode; + begin + LApplicationItem := ATreeview.Items.AddChild(LEngineItem + , AApplication.Name + ' [' + AApplication.BasePath + ']' + ); + + AApplication.EnumerateResources( + procedure (AName: string; AInfo: TMARSConstructorInfo) + begin + ATreeview.Items.AddChild( + LApplicationItem + , AInfo.TypeTClass.ClassName + ' [' + AName + ']' + ); + + end + ); + end + ); + end + ); + + if ATreeView.Items.Count > 0 then + ATreeView.Items[0].Expand(True); + finally + ATreeView.Items.EndUpdate; + end; +end; + +procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); +begin + StopServerAction.Execute; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + PortNumberEdit.Text := IntToStr(TServerEngine.Default.Port); + RenderEngines(MainTreeView); + StartServerAction.Execute; +end; + +procedure TMainForm.PortNumberEditChange(Sender: TObject); +begin + TServerEngine.Default.Port := StrToInt(PortNumberEdit.Text); +end; + +procedure TMainForm.StartServerActionExecute(Sender: TObject); +begin + // http server implementation + FServer := TMARShttpServerIndy.Create(TServerEngine.Default); + try + FServer.DefaultPort := TServerEngine.Default.Port; + FServer.Active := True; + except + FServer.Free; + raise; + end; +end; + +procedure TMainForm.StartServerActionUpdate(Sender: TObject); +begin + StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); +end; + +procedure TMainForm.StopServerActionExecute(Sender: TObject); +begin + FServer.Active := False; + FreeAndNil(FServer); +end; + +procedure TMainForm.StopServerActionUpdate(Sender: TObject); +begin + StopServerAction.Enabled := Assigned(FServer) and (FServer.Active = True); +end; + +end. diff --git a/Demos/MARSTemplateDCS/Server.Ignition.pas b/Demos/MARSTemplateDCS/Server.Ignition.pas new file mode 100644 index 00000000..93fbdc6c --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.Ignition.pas @@ -0,0 +1,132 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Ignition; + +{$I MARS.inc} + +interface + +uses + Classes, SysUtils, Rtti + , MARS.Core.Engine +; + +type + TServerEngine=class + private + class var FEngine: TMARSEngine; +{$IFDEF MARS_FIREDAC} + class var FAvailableConnectionDefs: TArray; +{$ENDIF} + public + class constructor CreateEngine; + class destructor DestroyEngine; + class property Default: TMARSEngine read FEngine; + end; + + +implementation + +uses + MARS.Core.Activation, MARS.Core.Activation.Interfaces + , MARS.Core.Application, MARS.Core.Utils, MARS.Utils.Parameters.IniFile + , MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyWriters + , MARS.Core.MessageBodyReaders, MARS.Data.MessageBodyWriters + {$IFDEF MARS_FIREDAC} , MARS.Data.FireDAC {$ENDIF} + {$IFDEF MSWINDOWS} , MARS.mORMotJWT.Token {$ELSE} , MARS.JOSEJWT.Token {$ENDIF} + , Server.Resources + ; + +{ TServerEngine } + +class constructor TServerEngine.CreateEngine; +begin + FEngine := TMARSEngine.Create; + try + // Engine configuration + FEngine.Parameters.LoadFromIniFile; + + // Application configuration + FEngine.AddApplication('DefaultApp', '/default', [ 'Server.Resources.*']); +{$IFDEF MARS_FIREDAC} + FAvailableConnectionDefs := TMARSFireDAC.LoadConnectionDefs(FEngine.Parameters, 'FireDAC'); +{$ENDIF} +{$REGION 'BeforeHandleRequest example'} +(* + FEngine.BeforeHandleRequest := + function (const AEngine: TMARSEngine; + const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; + var Handled: Boolean + ): Boolean + begin + Result := True; +{ + // skip favicon requests (browser) + if SameText(AURL.Document, 'favicon.ico') then + begin + Result := False; + Handled := True; + end; +} +{ + // Handle CORS and PreFlight + if SameText(ARequest.Method, 'OPTIONS') then + begin + Handled := True; + Result := False; + end; +} + end; +*) +{$ENDREGION} +{$REGION 'Global BeforeInvoke handler example'} +(* + // to execute something before each activation + TMARSActivation.RegisterBeforeInvoke( + procedure (const AActivation: IMARSActivation; out AIsAllowed: Boolean) + begin + + end + ); +*) +{$ENDREGION} +{$REGION 'Global AfterInvoke handler example'} +(* + // to execute something after each activation + TMARSActivation.RegisterAfterInvoke( + procedure (const AActivation: IMARSActivation) + begin + + end + ); +*) +{$ENDREGION} +{$REGION 'Global InvokeError handler example'} +(* + // to execute something on error + TMARSActivation.RegisterInvokeError( + procedure (const AActivation: IMARSActivation; const AException: Exception; var AHandled: Boolean) + begin + + end + ); +*) +{$ENDREGION} + except + FreeAndNil(FEngine); + raise; + end; +end; + +class destructor TServerEngine.DestroyEngine; +begin +{$IFDEF MARS_FIREDAC} + TMARSFireDAC.CloseConnectionDefs(FAvailableConnectionDefs); +{$ENDIF} + FreeAndNil(FEngine); +end; + +end. diff --git a/Demos/MARSTemplateDCS/Server.Resources.pas b/Demos/MARSTemplateDCS/Server.Resources.pas new file mode 100644 index 00000000..caafa682 --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.Resources.pas @@ -0,0 +1,48 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Resources; + +interface + +uses + SysUtils, Classes + + , MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.JSON, MARS.Core.Response + , MARS.Core.URL + + , MARS.Core.Token.Resource //, MARS.Core.Token +; + +type + [Path('helloworld')] + THelloWorldResource = class + protected + public + [GET, Produces(TMediaType.TEXT_PLAIN)] + function SayHelloWorld: string; + end; + + [Path('token')] + TTokenResource = class(TMARSTokenResource) + end; + +implementation + +uses + MARS.Core.Registry +; + +{ THelloWorldResource } + +function THelloWorldResource.SayHelloWorld: string; +begin + Result := 'Hello World!'; +end; + +initialization + TMARSResourceRegistry.Instance.RegisterResource; + TMARSResourceRegistry.Instance.RegisterResource; +end. diff --git a/Demos/MARSTemplateDCS/Server.Service.dfm b/Demos/MARSTemplateDCS/Server.Service.dfm new file mode 100644 index 00000000..e697b752 --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.Service.dfm @@ -0,0 +1,10 @@ +object ServerService: TServerService + OldCreateOrder = False + OnCreate = ServiceCreate + OnDestroy = ServiceDestroy + DisplayName = 'MARSTemplate Service' + OnStart = ServiceStart + OnStop = ServiceStop + Height = 150 + Width = 215 +end diff --git a/Demos/MARSTemplateDCS/Server.Service.pas b/Demos/MARSTemplateDCS/Server.Service.pas new file mode 100644 index 00000000..86c5a5ca --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.Service.pas @@ -0,0 +1,124 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Service; + +{$I MARS.inc} + +interface + +uses +{$ifdef DelphiXE3_UP} + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics +, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs +//, IPPeerServer, IPPeerAPI +, IdHTTPWebBrokerBridge, Web.WebReq, Web.WebBroker +{$else} + Windows, Messages, SysUtils, Classes, Graphics +, Controls, SvcMgr, Dialogs +//, IPPeerServer, IPPeerAPI +, IdHTTPWebBrokerBridge, WebReq, WebBroker +{$endif} +, IdContext +, MARS.http.Server.DCS +; + +type + TServerService = class(TService) + procedure ServiceCreate(Sender: TObject); + procedure ServiceDestroy(Sender: TObject); + procedure ServiceStart(Sender: TService; var Started: Boolean); + procedure ServiceStop(Sender: TService; var Stopped: Boolean); + private + FServer: TIdHTTPWebBrokerBridge; + + procedure ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); virtual; + + public + function GetServiceController: TServiceController; override; + + const DEFAULT_PORT = 8080; + end; + +var + ServerService: TServerService; + +implementation + +{$R *.dfm} + +uses + IdSchedulerOfThreadPool +, Server.Ignition +, Server.WebModule +; + +procedure ServiceController(CtrlCode: DWord); stdcall; +begin + ServerService.Controller(CtrlCode); +end; + +function TServerService.GetServiceController: TServiceController; +begin + Result := ServiceController; +end; + +procedure TServerService.ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); +begin + // Allow JWT Bearer authentication's scheme + if SameText(AAuthType, 'Bearer') then + VHandled := True; +end; + +procedure TServerService.ServiceCreate(Sender: TObject); +var + LScheduler: TIdSchedulerOfThreadPool; +begin + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + + FServer := TIdHTTPWebBrokerBridge.Create(nil); + try + FServer.DefaultPort := TServerEngine.Default.Port; + + LScheduler := TIdSchedulerOfThreadPool.Create(FServer); + try + LScheduler.PoolSize := TServerEngine.Default.ThreadPoolSize; + FServer.Scheduler := LScheduler; + FServer.MaxConnections := LScheduler.PoolSize; + FServer.OnParseAuthentication := ParseAuthenticationHandler; + except + FServer.Scheduler.Free; + FServer.Scheduler := nil; + raise; + end; + except + FServer.Free; + raise; + end; +end; + +procedure TServerService.ServiceDestroy(Sender: TObject); +begin + FreeAndNil(FServer); +end; + +procedure TServerService.ServiceStart(Sender: TService; var Started: Boolean); +begin + FServer.Active := True; + Started := FServer.Active; +end; + +procedure TServerService.ServiceStop(Sender: TService; var Stopped: Boolean); +begin + FServer.Active := False; + Stopped := not FServer.Active; +end; + +end. diff --git a/Demos/MARSTemplateDCS/Server.WebModule.dfm b/Demos/MARSTemplateDCS/Server.WebModule.dfm new file mode 100644 index 00000000..a589a41a --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.WebModule.dfm @@ -0,0 +1,12 @@ +object ServerWebModule: TServerWebModule + OldCreateOrder = False + Actions = < + item + Default = True + Name = 'DefaultHandler' + PathInfo = '/' + OnAction = ServerWebModuleDefaultHandlerAction + end> + Height = 230 + Width = 415 +end diff --git a/Demos/MARSTemplateDCS/Server.WebModule.pas b/Demos/MARSTemplateDCS/Server.WebModule.pas new file mode 100644 index 00000000..72908095 --- /dev/null +++ b/Demos/MARSTemplateDCS/Server.WebModule.pas @@ -0,0 +1,57 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.WebModule; + +{$I MARS.inc} + +interface + +uses System.SysUtils, System.Classes, Web.HTTPApp; + +type + TServerWebModule = class(TWebModule) + procedure ServerWebModuleDefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + private + { Private declarations } + public + { Public declarations } + end; + +var + WebModuleClass: TComponentClass = TServerWebModule; + +implementation + +{%CLASSGROUP 'System.Classes.TPersistent'} + +{$R *.dfm} + +uses + MARS.http.Server.Indy +, Server.Ignition; + +procedure TServerWebModule.ServerWebModuleDefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +begin + inherited; + + if not TServerEngine.Default.HandleRequest(TMARSWebRequest.Create(Request), TMARSWebResponse.Create(Response)) then + begin + Response.ContentType := 'application/json'; + Response.Content := + '{"success": false, "details": ' + + '{' + + '"error": "Request not found",' + + '"pathinfo": "' + Request.PathInfo + '"' + + '}' + + '}'; + end + else + Handled := True; +end; + +end. diff --git a/Demos/MARSTemplateDCS/ServerConst.pas b/Demos/MARSTemplateDCS/ServerConst.pas new file mode 100644 index 00000000..49558197 --- /dev/null +++ b/Demos/MARSTemplateDCS/ServerConst.pas @@ -0,0 +1,42 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit ServerConst; + +interface + +resourcestring + sPortInUse = '- Error: Port %s already in use'; + sPortSet = '- Port set to %s'; + sServerRunning = '- The Server is already running'; + sStartingServer = '- Starting HTTP Server on port %d'; + sStoppingServer = '- Stopping Server'; + sServerStopped = '- Server Stopped'; + sServerNotRunning = '- The Server is not running'; + sInvalidCommand = '- Error: Invalid Command'; + sIndyVersion = '- Indy Version: '; + sActive = '- Active: '; + sPort = '- Port: '; + sSessionID = '- Session ID CookieName: '; + sCommands = 'Enter a Command: ' + slineBreak + + ' - "start" to start the server'+ slineBreak + + ' - "stop" to stop the server'+ slineBreak + + ' - "set port" to change the default port'+ slineBreak + + ' - "status" for Server status'+ slineBreak + + ' - "help" to show commands'+ slineBreak + + ' - "exit" to close the application'; + +const + cArrow = '->'; + cCommandStart = 'start'; + cCommandStop = 'stop'; + cCommandStatus = 'status'; + cCommandHelp = 'help'; + cCommandSetPort = 'set port'; + cCommandExit = 'exit'; + +implementation + +end. diff --git a/Demos/MARSTemplateDCS/bin/MARSTemplateServerApplication.ini b/Demos/MARSTemplateDCS/bin/MARSTemplateServerApplication.ini new file mode 100644 index 00000000..e098e5da --- /dev/null +++ b/Demos/MARSTemplateDCS/bin/MARSTemplateServerApplication.ini @@ -0,0 +1,2 @@ +[DefaultEngine] +ThreadPoolSize=100 \ No newline at end of file diff --git a/Demos/MultipartFormData/MultipartFormDataServerApplication.dproj b/Demos/MultipartFormData/MultipartFormDataServerApplication.dproj index c3661c21..a9a4f3c5 100644 --- a/Demos/MultipartFormData/MultipartFormDataServerApplication.dproj +++ b/Demos/MultipartFormData/MultipartFormDataServerApplication.dproj @@ -3,7 +3,7 @@ {0A5E1DDC-90B4-4B41-A7DC-2B0FC45D4349} MultipartFormDataServerApplication.dpr True - Release + Debug 3 Application VCL diff --git a/Demos/MultipartFormData/Server.Ignition.pas b/Demos/MultipartFormData/Server.Ignition.pas index 7151c084..d3d7c467 100644 --- a/Demos/MultipartFormData/Server.Ignition.pas +++ b/Demos/MultipartFormData/Server.Ignition.pas @@ -35,6 +35,7 @@ implementation , MARS.Core.Application, MARS.Core.Utils, MARS.Utils.Parameters.IniFile , MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyWriters , MARS.Core.MessageBodyReaders, MARS.Data.MessageBodyWriters + , MARS.Core.RequestAndResponse.Interfaces {$IFDEF MARS_FIREDAC} , MARS.Data.FireDAC {$ENDIF} {$IFDEF MSWINDOWS} , MARS.mORMotJWT.Token {$ELSE} , MARS.JOSEJWT.Token {$ENDIF} , MARS.Core.URL, Web.HttpApp @@ -58,8 +59,8 @@ implementation {$REGION 'BeforeHandleRequest example'} FEngine.BeforeHandleRequest := - function (const AEngine: TMARSEngine; const AURL: TMARSURL; - const ARequest: TWebRequest; const AResponse: TWebResponse; + function (const AEngine: TMARSEngine; + const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; var Handled: Boolean): Boolean begin Result := True; diff --git a/Demos/Mustache/MARSMustacheServerApacheModule.dproj b/Demos/Mustache/MARSMustacheServerApacheModule.dproj index 1888c829..9aaa3c93 100644 --- a/Demos/Mustache/MARSMustacheServerApacheModule.dproj +++ b/Demos/Mustache/MARSMustacheServerApacheModule.dproj @@ -1,7 +1,7 @@  {F003A08E-799F-4C20-8128-819AAAAC0A88} - 18.2 + 18.8 None MARSMustacheServerApacheModule.dpr True @@ -124,19 +124,19 @@ - - - MARSMustacheServerApacheModule.dll + + true - - + + + libMARSMustacheServerApacheModule.so true - - + + true @@ -145,14 +145,24 @@ true + + + MARSMustacheServerApacheModule.dll + true + + + + + true + + true - - - libMARSMustacheServerApacheModule.so + + true @@ -161,7 +171,6 @@ 1 - Contents\MacOS 0
@@ -170,6 +179,20 @@ classes 1 + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + @@ -182,90 +205,242 @@ 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\values 1 + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -283,6 +458,10 @@ 1 .framework + + 1 + .framework + 0 @@ -292,6 +471,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -314,6 +497,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -323,6 +510,9 @@ 0 + + 0 + 0 @@ -335,6 +525,9 @@ 0 + + 0 + 0 @@ -350,6 +543,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -361,6 +565,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -372,6 +609,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -383,6 +675,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -416,10 +818,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -445,6 +872,7 @@ 1 + @@ -452,12 +880,20 @@ Contents\Resources 1 + + Contents\Resources + 1 + library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -473,10 +909,19 @@ 1 + + 1 + 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -512,7 +957,9 @@ + + True diff --git a/Demos/Mustache/MARSMustacheServerApplication.dproj b/Demos/Mustache/MARSMustacheServerApplication.dproj index 3f68e7e3..b8dfba51 100644 --- a/Demos/Mustache/MARSMustacheServerApplication.dproj +++ b/Demos/Mustache/MARSMustacheServerApplication.dproj @@ -7,7 +7,7 @@ Application VCL DCC32 - 18.2 + 18.8 1 Win32 @@ -167,7 +167,6 @@ 1 - Contents\MacOS 0 @@ -176,6 +175,20 @@ classes 1 + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + @@ -188,90 +201,242 @@ 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\values 1 + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -289,6 +454,10 @@ 1 .framework + + 1 + .framework + 0 @@ -298,6 +467,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -320,6 +493,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -329,6 +506,9 @@ 0 + + 0 + 0 @@ -341,6 +521,9 @@ 0 + + 0 + 0 @@ -356,6 +539,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -367,6 +561,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -378,6 +605,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -389,6 +671,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -422,10 +814,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -451,6 +868,7 @@ 1 + @@ -458,12 +876,20 @@ Contents\Resources 1 + + Contents\Resources + 1 + library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -479,10 +905,19 @@ 1 + + 1 + 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -518,7 +953,9 @@ + + 12 diff --git a/Demos/Mustache/MARSMustacheServerApplication.res b/Demos/Mustache/MARSMustacheServerApplication.res index da24bfd707ddf8ffc3b7ad123b2a0a4da2fdcaff..aff75aeb20d9f85f9fc0644b9444884261577bc7 100644 GIT binary patch delta 25 hcmbPmmbvFF^M;TIjMq1ZJ~+*^S?RG3^W=bM4gjK)3?2Xg delta 27 jcmeA<%RJ#M^M;TIj5jxjJ~+*^S?;kOGb6)f?`MtxwzCWy diff --git a/Demos/Mustache/MARSMustacheServerConsoleApplication.dproj b/Demos/Mustache/MARSMustacheServerConsoleApplication.dproj index 5b8c88d6..93f8e961 100644 --- a/Demos/Mustache/MARSMustacheServerConsoleApplication.dproj +++ b/Demos/Mustache/MARSMustacheServerConsoleApplication.dproj @@ -1,7 +1,7 @@  {3989725D-F9E4-4178-9FBC-93DC284F63C7} - 18.2 + 18.8 None MARSMustacheServerConsoleApplication.dpr True @@ -130,12 +130,22 @@ true
+ + + true + + + + + true + + true - + true @@ -150,7 +160,6 @@ 1 - Contents\MacOS 0 @@ -159,6 +168,20 @@ classes 1 + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + @@ -171,90 +194,242 @@ 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\values 1 + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -272,6 +447,10 @@ 1 .framework + + 1 + .framework + 0 @@ -281,6 +460,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -303,6 +486,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -312,6 +499,9 @@ 0 + + 0 + 0 @@ -324,6 +514,9 @@ 0 + + 0 + 0 @@ -339,6 +532,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -350,6 +554,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -361,6 +598,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -372,6 +664,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -405,10 +807,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -434,6 +861,7 @@ 1 + @@ -441,12 +869,20 @@ Contents\Resources 1 + + Contents\Resources + 1 + library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -462,10 +898,19 @@ 1 + + 1 + 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -501,7 +946,9 @@ + + True diff --git a/Demos/Mustache/MARSMustacheServerFMXApplication.dproj b/Demos/Mustache/MARSMustacheServerFMXApplication.dproj index 79bf2807..9260c1bf 100644 --- a/Demos/Mustache/MARSMustacheServerFMXApplication.dproj +++ b/Demos/Mustache/MARSMustacheServerFMXApplication.dproj @@ -1,13 +1,13 @@  {E0F4FA79-1B8C-437A-AB05-73EF7AD349A8} - 18.2 + 18.8 FMX MARSMustacheServerFMXApplication.dpr True Debug Win32 - 1119 + 37983 Application @@ -18,6 +18,11 @@ Base true + + true + Base + true + true Base @@ -38,6 +43,11 @@ Base true + + true + Base + true + true Base @@ -120,6 +130,30 @@ $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png Debug package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + DBXSqliteDriver;RESTComponents;DBXInterBaseDriver;emsclientfiredac;DataSnapFireDAC;tethering;bindcompfmx;FmxTeeUI;FireDACIBDriver;fmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;soapserver;bindengine;CloudService;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FireDAC;FireDACSqliteDriver;ibmonitor;FMXTee;soaprtl;DbxCommonDriver;ibxpress;xmlrtl;soapmidas;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage);$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png @@ -145,7 +179,7 @@ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png iPhoneAndiPad $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png @@ -162,6 +196,22 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png Debug + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png @@ -187,7 +237,7 @@ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png iPhoneAndiPad $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png @@ -204,6 +254,22 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png Debug + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png @@ -234,7 +300,7 @@ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png @@ -244,6 +310,22 @@ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png @@ -254,8 +336,23 @@ /usr/X11/bin/xterm -e "%debuggee%" DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXInterBaseDriver;emsclientfiredac;DataSnapFireDAC;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;FireDACIBDriver;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;soapserver;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) true - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user Debug + true + true + Base + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + .\bin\$(Platform) + .\lib\$(Platform) + (None) + /usr/X11/bin/xterm -e "%debuggee%" + DBXSqliteDriver;RESTComponents;DataSnapServerMidas;DBXInterBaseDriver;emsclientfiredac;DataSnapFireDAC;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;FireDACIBDriver;fmx;fmxdae;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;emsclient;FireDACCommon;RESTBackendComponents;soapserver;bindengine;DBXMySQLDriver;FireDACOracleDriver;CloudService;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FireDAC;emshosting;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage);$(DCC_UsePackage) .\lib\$(Platform) @@ -295,12 +392,12 @@ true 1033 true - true false + PerMonitor true - true + PerMonitor false @@ -310,11 +407,11 @@ true - true + PerMonitor true - true + PerMonitor @@ -359,13 +456,19 @@ true - + + MARSTemplateServerFMXApplication.icns true - - + + + true + + + + true @@ -375,37 +478,41 @@ true - - + + true - - + + + Assets\ + Logo150x150.png true - + - MARSTemplateServerFMXApplication.icns true - + - Info.plist true - - + + true - + + + true + + + - Assets\ - Logo150x150.png + Info.plist true @@ -424,7 +531,6 @@ 1 - Contents\MacOS 0 @@ -433,6 +539,20 @@ classes 1 + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + @@ -445,90 +565,242 @@ 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\values 1 + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -548,6 +820,11 @@ 1 .framework + + Contents\MacOS + 1 + .framework + 0 @@ -570,6 +847,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .dll;.bpl @@ -593,6 +875,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .bpl @@ -602,6 +889,9 @@ 0 + + 0 + 0 @@ -615,6 +905,10 @@ Contents\Resources\StartUp\ 0 + + Contents\Resources\StartUp\ + 0 + 0 @@ -630,6 +924,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -641,6 +946,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -652,6 +990,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -663,6 +1056,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -696,10 +1199,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -751,29 +1279,51 @@ 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + ..\ 1 + + ..\ + 1 + Contents 1 + + Contents + 1 + Contents\Resources 1 + + Contents\Resources + 1 + library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -790,10 +1340,20 @@ Contents\MacOS 1 + + Contents\MacOS + 1 + 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -829,14 +1389,18 @@ + + True + True True True True True + True True True diff --git a/Demos/Mustache/MARSMustacheServerISAPI.dproj b/Demos/Mustache/MARSMustacheServerISAPI.dproj index 4a13f336..966175f9 100644 --- a/Demos/Mustache/MARSMustacheServerISAPI.dproj +++ b/Demos/Mustache/MARSMustacheServerISAPI.dproj @@ -1,7 +1,7 @@  {7F50F5E9-ED2D-4A28-9C9E-10FAA1B36C4D} - 18.2 + 18.8 VCL MARSMustacheServerISAPI.dpr True @@ -134,7 +134,6 @@ 1 - Contents\MacOS 0 @@ -143,6 +142,20 @@ classes 1 + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + @@ -155,90 +168,242 @@ 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\values 1 + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -256,6 +421,10 @@ 1 .framework + + 1 + .framework + 0 @@ -265,6 +434,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -287,6 +460,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -296,6 +473,9 @@ 0 + + 0 + 0 @@ -308,6 +488,9 @@ 0 + + 0 + 0 @@ -323,6 +506,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -334,6 +528,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -345,6 +572,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -356,6 +638,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -389,10 +781,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -418,6 +835,7 @@ 1 + @@ -425,12 +843,20 @@ Contents\Resources 1 + + Contents\Resources + 1 + library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -446,10 +872,19 @@ 1 + + 1 + 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -485,7 +920,9 @@ + + True diff --git a/Demos/Mustache/MARSMustacheServerService.dproj b/Demos/Mustache/MARSMustacheServerService.dproj index d5adb61d..31093e5b 100644 --- a/Demos/Mustache/MARSMustacheServerService.dproj +++ b/Demos/Mustache/MARSMustacheServerService.dproj @@ -1,7 +1,7 @@  {F72901B8-3DC2-48DB-9F80-EE7BA471758C} - 18.2 + 18.8 VCL MARSMustacheServerService.dpr True @@ -82,10 +82,10 @@ true - true 1033 true false + PerMonitor false @@ -94,8 +94,8 @@ 0 - true true + PerMonitor @@ -154,7 +154,6 @@ 1 - Contents\MacOS 0 @@ -163,6 +162,20 @@ classes 1 + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + @@ -175,90 +188,242 @@ 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\values 1 + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -278,6 +443,11 @@ 1 .framework + + Contents\MacOS + 1 + .framework + 0 @@ -300,6 +470,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .dll;.bpl @@ -323,6 +498,11 @@ 1 .dylib + + Contents\MacOS + 1 + .dylib + 0 .bpl @@ -332,6 +512,9 @@ 0 + + 0 + 0 @@ -345,6 +528,10 @@ Contents\Resources\StartUp\ 0 + + Contents\Resources\StartUp\ + 0 + 0 @@ -360,6 +547,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -371,6 +569,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -382,6 +613,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -393,6 +679,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -426,10 +822,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -481,29 +902,51 @@ 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + ..\ 1 + + ..\ + 1 + Contents 1 + + Contents + 1 + Contents\Resources 1 + + Contents\Resources + 1 + library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -520,10 +963,20 @@ Contents\MacOS 1 + + Contents\MacOS + 1 + 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -559,7 +1012,9 @@ + + True diff --git a/Demos/Mustache/Server.Forms.Main.pas b/Demos/Mustache/Server.Forms.Main.pas index c587bfee..03a5f3cf 100644 --- a/Demos/Mustache/Server.Forms.Main.pas +++ b/Demos/Mustache/Server.Forms.Main.pas @@ -44,7 +44,7 @@ implementation uses StrUtils, Web.HttpApp - , MARS.Core.URL, MARS.Core.Engine + , MARS.Core.URL, MARS.Core.Engine, MARS.Core.RequestAndResponse.Interfaces , Server.Ignition; procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); @@ -59,7 +59,7 @@ procedure TMainForm.FormCreate(Sender: TObject); // skip favicon requests (browser) TServerEngine.Default.BeforeHandleRequest := function (const AEngine: TMARSEngine; - const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; + const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; var Handled: Boolean ): Boolean begin diff --git a/Demos/RemoteMic/FMXClient.DataModules.Main.dfm b/Demos/RemoteMic/FMXClient.DataModules.Main.dfm new file mode 100644 index 00000000..650df259 --- /dev/null +++ b/Demos/RemoteMic/FMXClient.DataModules.Main.dfm @@ -0,0 +1,25 @@ +object MainDataModule: TMainDataModule + OldCreateOrder = False + Height = 411 + Width = 518 + object MARSApplication: TMARSClientApplication + DefaultMediaType = 'application/json' + DefaultContentType = 'application/json' + Client = MARSClient + Left = 88 + Top = 80 + end + object MARSClient: TMARSNetClient + MARSEngineURL = 'http://localhost:8080/rest' + ConnectTimeout = 60000 + ReadTimeout = 60000 + HttpClient.Asynchronous = False + HttpClient.ConnectionTimeout = 60000 + HttpClient.ResponseTimeout = 60000 + HttpClient.AllowCookies = True + HttpClient.HandleRedirects = True + HttpClient.UserAgent = 'Embarcadero URI Client/1.0' + Left = 88 + Top = 24 + end +end diff --git a/Demos/RemoteMic/FMXClient.DataModules.Main.pas b/Demos/RemoteMic/FMXClient.DataModules.Main.pas new file mode 100644 index 00000000..a4a66361 --- /dev/null +++ b/Demos/RemoteMic/FMXClient.DataModules.Main.pas @@ -0,0 +1,32 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit FMXClient.DataModules.Main; + +interface + +uses + System.SysUtils, System.Classes, MARS.Client.Application, + MARS.Client.Client, MARS.Client.Client.Net +; + +type + TMainDataModule = class(TDataModule) + MARSApplication: TMARSClientApplication; + MARSClient: TMARSNetClient; + private + public + end; + +var + MainDataModule: TMainDataModule; + +implementation + +{%CLASSGROUP 'FMX.Controls.TControl'} + +{$R *.dfm} + +end. diff --git a/Demos/RemoteMic/FMXClient.DataModules.Main.vlb b/Demos/RemoteMic/FMXClient.DataModules.Main.vlb new file mode 100644 index 00000000..95e7a6a2 --- /dev/null +++ b/Demos/RemoteMic/FMXClient.DataModules.Main.vlb @@ -0,0 +1,10 @@ +[MARSApplication] +Coordinates=150,53,96,33 + +[] +Coordinates=71,70,69,33 +Visible=False + +[MainForm.BindSourceDB1] +Coordinates=0,0,144,267 + diff --git a/Demos/RemoteMic/FMXClient.Forms.Main.fmx b/Demos/RemoteMic/FMXClient.Forms.Main.fmx new file mode 100644 index 00000000..9990bf0e --- /dev/null +++ b/Demos/RemoteMic/FMXClient.Forms.Main.fmx @@ -0,0 +1,27 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'MARS Template Client' + ClientHeight = 480 + ClientWidth = 640 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + DesignerMasterStyle = 0 + object TopToolBar: TToolBar + Size.Width = 640.000000000000000000 + Size.Height = 48.000000000000000000 + Size.PlatformDefault = False + TabOrder = 0 + object TitleLabel: TLabel + Align = Center + AutoSize = True + Size.Width = 121.000000000000000000 + Size.Height = 16.000000000000000000 + Size.PlatformDefault = False + StyleLookup = 'toollabel' + TextSettings.WordWrap = False + Text = 'MARS Template Client' + end + end +end diff --git a/Demos/RemoteMic/FMXClient.Forms.Main.pas b/Demos/RemoteMic/FMXClient.Forms.Main.pas new file mode 100644 index 00000000..1b939e4d --- /dev/null +++ b/Demos/RemoteMic/FMXClient.Forms.Main.pas @@ -0,0 +1,34 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit FMXClient.Forms.Main; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Layouts, FMX.Controls.Presentation; + +type + TMainForm = class(TForm) + TopToolBar: TToolBar; + TitleLabel: TLabel; + private + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.fmx} + +uses + FMXClient.DataModules.Main + ; + +end. diff --git a/Demos/RemoteMic/FMXClient.Forms.Main.vlb b/Demos/RemoteMic/FMXClient.Forms.Main.vlb new file mode 100644 index 00000000..b439f67e --- /dev/null +++ b/Demos/RemoteMic/FMXClient.Forms.Main.vlb @@ -0,0 +1,26 @@ +[TopToolBar] +Coordinates=317,78,82,36 + +[MainDataModule.ItemsQueryDataSet] +Coordinates=10,10,228,212 + +[] +Coordinates=145,78,71,36 +Visible=True + +[TitleLabel] +Coordinates=236,78,71,58 + +[MainDataModule.] +Coordinates=472,428,215,249 +Visible=False + +[MainDataModule.EmployeeQuery1] +Visible=False + +[MainDataModule.EmployeeQueryDataSet] +Coordinates=100,10,253,58 + +[MainDataModule.CountryByName1] +Coordinates=10,78,216,58 + diff --git a/Demos/RemoteMic/RemoteMicClient.dpr b/Demos/RemoteMic/RemoteMicClient.dpr new file mode 100644 index 00000000..52adba22 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicClient.dpr @@ -0,0 +1,21 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) + program RemoteMicClient; + +uses + System.StartUpCopy, + FMX.Forms, + FMXClient.Forms.Main in 'FMXClient.Forms.Main.pas' {MainForm}, + FMXClient.DataModules.Main in 'FMXClient.DataModules.Main.pas' {MainDataModule: TDataModule}; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainDataModule, MainDataModule); + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/RemoteMic/RemoteMicClient.dproj b/Demos/RemoteMic/RemoteMicClient.dproj new file mode 100644 index 00000000..5e28a596 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicClient.dproj @@ -0,0 +1,1423 @@ + + + {B6B860BA-E3A5-48E9-8EC6-7FE516AE7FC4} + RemoteMicClient.dpr + True + Debug + 33937 + Application + FMX + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + RemoteMicClient + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + .\bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + + + $(BDS)\bin\delphi_PROJECTICNS.icns + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + true + Base + true + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + RemoteMicServer_Icon.ico + + + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + true + + + Debug + + + true + + + $(BDS)\bin\delphi_PROJECTICNS.icns + + + true + true + Cfg_2 + true + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + RemoteMicServer_Icon.ico + PerMonitor + + + + MainSource + + +
MainForm
+
+ +
MainDataModule
+ TDataModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + RemoteMicClient.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + True + False + True + False + True + False + False + True + False + + + + + true + + + + + ic_launcher.png + true + + + + + ic_launcher.png + true + + + + + libRemoteMicClient.so + true + + + + + ic_launcher.png + true + + + + + true + + + + + true + + + + + splash_image.png + true + + + + + true + + + + + splash_image.png + true + + + + + splash_image.png + true + + + + + splash_image.png + true + + + + + true + + + + + libRemoteMicClient.so + true + + + + + true + + + + + classes.dex + true + + + + + ic_launcher.png + true + + + + + ic_launcher.png + true + + + + + true + + + + + true + + + + + libRemoteMicClient.so + true + + + + + true + + + + + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/RemoteMic/RemoteMicProjectGroup.groupproj b/Demos/RemoteMic/RemoteMicProjectGroup.groupproj new file mode 100644 index 00000000..bde30c09 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicProjectGroup.groupproj @@ -0,0 +1,120 @@ + + + {6E23DEFF-F737-42C3-B8AD-2549B8F67C93} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demos/RemoteMic/RemoteMicServerApacheModule.dpr b/Demos/RemoteMic/RemoteMicServerApacheModule.dpr new file mode 100644 index 00000000..6185e2cd --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerApacheModule.dpr @@ -0,0 +1,54 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +library RemoteMicServerApacheModule; + +uses + {$IFDEF MSWINDOWS} + Winapi.ActiveX, System.Win.ComObj, + {$ENDIF } + Web.WebBroker, + Web.ApacheApp, + Web.HTTPD24Impl, + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas'; + +{$R *.res} + +// httpd.conf entries: +// +(* + LoadModule marstemplate_module modules/mod_marstemplate.dll + + + SetHandler mod_marstemplate-handler + +*) +// +// These entries assume that the output directory for this project is the apache/modules directory. +// +// httpd.conf entries should be different if the project is changed in these ways: +// 1. The TApacheModuleData variable name is changed. +// 2. The project is renamed. +// 3. The output directory is not the apache/modules directory. +// 4. The dynamic library extension depends on a platform. Use .dll on Windows and .so on Linux. +// + +// Declare exported variable so that Apache can access this module. +var + GModuleData: TApacheModuleData; +exports + GModuleData name 'marstemplate_module'; + +begin +{$IFDEF MSWINDOWS} + CoInitFlags := COINIT_MULTITHREADED; +{$ENDIF} + Web.ApacheApp.InitApplication(@GModuleData); + Application.Initialize; + Application.WebModuleClass := WebModuleClass; + Application.Run; +end. diff --git a/Demos/RemoteMic/RemoteMicServerApacheModule.dproj b/Demos/RemoteMic/RemoteMicServerApacheModule.dproj new file mode 100644 index 00000000..1f6cea83 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerApacheModule.dproj @@ -0,0 +1,985 @@ + + + {0D982E91-6C92-4321-9078-458448B01536} + 18.8 + None + RemoteMicServerApacheModule.dpr + True + Release + Win32 + 129 + Library + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\lib\$(Platform)\$(Config) + .\bin + false + false + false + false + false + true + RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage) + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + RemoteMicServerApacheModule + + + DataSnapServerMidas;FireDACADSDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;inetdb;emsedge;dbexpress;IndyCore;dsnap;DataSnapCommon;DataSnapConnectors;bindengine;FireDACOracleDriver;FireDACMySQLDriver;FireDACCommonODBC;DataSnapClient;IndySystem;FireDACDb2Driver;FireDACInfxDriver;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;rtl;DbxClientDriver;CustomIPTransport;bindcomp;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;dbrtl;FireDACMongoDBDriver;IndyProtocols;$(DCC_UsePackage) + true + /usr/bin/xterm -e "%debuggee%" + (None) + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;CodeSiteExpressPkg;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + 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 + (None) + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + true + 1033 + (None) + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + +
ServerWebModule
+ dfm + TWebModule +
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Library + + + + RemoteMicServerApacheModule.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + RemoteMicServerApacheModule.dll + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + libRemoteMicServerApacheModule.so + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + False + + + 12 + + + + +
diff --git a/Demos/RemoteMic/RemoteMicServerApplication.dpr b/Demos/RemoteMic/RemoteMicServerApplication.dpr new file mode 100644 index 00000000..da6b0674 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerApplication.dpr @@ -0,0 +1,23 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) + program RemoteMicServerApplication; + +uses + Forms, + Server.Forms.Main in 'Server.Forms.Main.pas' {MainForm}, + Server.Resources in 'Server.Resources.pas', + Server.Ignition in 'Server.Ignition.pas'; + +{$R *.res} + +begin + ReportMemoryLeaksOnShutdown := True; + + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/RemoteMic/RemoteMicServerApplication.dproj b/Demos/RemoteMic/RemoteMicServerApplication.dproj new file mode 100644 index 00000000..1ee97abd --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerApplication.dproj @@ -0,0 +1,190 @@ + + + {0A5E1DDC-90B4-4B41-A7DC-2B0FC45D4349} + RemoteMicServerApplication.dpr + True + Release + 3 + Application + VCL + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + RemoteMicServerApplication + Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + .\bin + .\lib\$(Platform)\$(Config) + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + RemoteMicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + RemoteMicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + RemoteMicServer_Icon.ico + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + RemoteMicServer_Icon.ico + PerMonitor + + + true + PerMonitor + + + + MainSource + + +
MainForm
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + RemoteMicServerApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + True + True + + + 12 + + + +
diff --git a/Demos/RemoteMic/RemoteMicServerConsoleApplication.dpr b/Demos/RemoteMic/RemoteMicServerConsoleApplication.dpr new file mode 100644 index 00000000..78192f3d --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerConsoleApplication.dpr @@ -0,0 +1,201 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program RemoteMicServerConsoleApplication; +{$APPTYPE CONSOLE} + +{$I MARS.inc} + +uses +{$ifdef DelphiXE3_UP} + System.SysUtils, + System.Types, +// IPPeerServer, IPPeerAPI, + IdHTTPWebBrokerBridge, + IdSchedulerOfThreadPool, + Web.WebReq, + Web.WebBroker, +{$else} + SysUtils, StrUtils, + Types, + IdHTTPWebBrokerBridge, + IdSchedulerOfThreadPool, + WebReq, + WebBroker, +{$endif} + IdContext, + ServerConst in 'ServerConst.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}, + Server.Ignition in 'Server.Ignition.pas'; + +{$R *.res} + +type + TDummyIndyServer = class + public + procedure ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); virtual; + end; + + +procedure StartServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if not (AServer.Active) then + begin + AServer.DefaultPort := TServerEngine.Default.Port; + Writeln(Format(sStartingServer, [AServer.DefaultPort])); + AServer.Active := True; + end + else + Writeln(sServerRunning); + Write(cArrow); +end; + +procedure StopServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if AServer.Active then + begin + Writeln(sStoppingServer); + AServer.Active := False; + Writeln(sServerStopped); + end + else + Writeln(sServerNotRunning); + Write(cArrow); +end; + +procedure SetPort(const AServer: TIdHTTPWebBrokerBridge; const APort: string); +var + LPort: Integer; + LWasActive: Boolean; +begin + LPort := StrToIntDef(APort, -1); + if LPort = -1 then + begin + Writeln('Port should be an integer number. Try again.'); + Exit; + end; + + LWasActive := AServer.Active; + if LWasActive then + StopServer(AServer); + TServerEngine.Default.Port := LPort; + if LWasActive then + StartServer(AServer); + Writeln(Format(sPortSet, [IntToStr(TServerEngine.Default.Port)])); + Write(cArrow); +end; + +procedure WriteCommands; +begin + Writeln(sCommands); + Write(cArrow); +end; + +procedure WriteStatus(const AServer: TIdHTTPWebBrokerBridge); +begin + Writeln(sIndyVersion + AServer.SessionList.Version); + Writeln(sActive + BoolToStr(AServer.Active, True)); + Writeln(sPort + IntToStr(TServerEngine.Default.Port)); + Write(cArrow); +end; + +procedure SetupThreadScheduler(const AServer: TIdHTTPWebBrokerBridge); +var + LScheduler: TIdSchedulerOfThreadPool; +begin + LScheduler := TIdSchedulerOfThreadPool.Create(AServer); + try + LScheduler.PoolSize := TServerEngine.Default.ThreadPoolSize; + AServer.Scheduler := LScheduler; + AServer.MaxConnections := LScheduler.PoolSize; + except + AServer.Scheduler.DisposeOf; + AServer.Scheduler := nil; + raise; + end; +end; + +procedure RunServer(); +var + LServer: TIdHTTPWebBrokerBridge; + LDummyIndy: TDummyIndyServer; + LResponse: string; +begin + WriteCommands; + LDummyIndy := TDummyIndyServer.Create; + try + LServer := TIdHTTPWebBrokerBridge.Create(nil); + try + LServer.DefaultPort := TServerEngine.Default.Port; + LServer.OnParseAuthentication := LDummyIndy.ParseAuthenticationHandler; + {$IFNDEF LINUX} + SetupThreadScheduler(LServer); + {$ENDIF} + + while True do + begin + Readln(LResponse); + LResponse := LowerCase(LResponse); + if sametext(LResponse, cCommandStart) then + StartServer(LServer) + else if sametext(LResponse, cCommandStatus) then + WriteStatus(LServer) + else if sametext(LResponse, cCommandStop) then + StopServer(LServer) + {$ifdef DelphiXE3_UP} + else if LResponse.StartsWith(cCommandSetPort, True) then + SetPort(LServer, LResponse.Split([' '])[2]) + {$else} + else if AnsiStartsText(cCommandSetPort, LResponse) then + SetPort(LServer, Copy(LResponse, Length(cCommandSetPort)+1, MAXINT)) + {$endif} + + else if sametext(LResponse, cCommandHelp) then + WriteCommands + else if sametext(LResponse, cCommandExit) then + if LServer.Active then + begin + StopServer(LServer); + break + end + else + break + else + begin + Writeln(sInvalidCommand); + Write(cArrow); + end; + end; + finally + LServer.Free; + end; + finally + LDummyIndy.Free; + end; +end; + +{ TDummyIndyServer } + +procedure TDummyIndyServer.ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); +begin + // Allow JWT Bearer authentication's scheme + if SameText(AAuthType, 'Bearer') then + VHandled := True; +end; + +begin + try + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + RunServer(); + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end +end. diff --git a/Demos/RemoteMic/RemoteMicServerConsoleApplication.dproj b/Demos/RemoteMic/RemoteMicServerConsoleApplication.dproj new file mode 100644 index 00000000..22bf0b90 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerConsoleApplication.dproj @@ -0,0 +1,1211 @@ + + + {8EEC8DF2-3740-468A-937A-60168245FB71} + RemoteMicServerConsoleApplication.dpr + True + Release + 4229 + Console + None + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + RemoteMicServerConsoleApplication + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + ./bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + RemoteMicServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + /usr/X11/bin/xterm -e "%debuggee%" + (None) + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + true + Base + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + + + 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=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + RemoteMicServer_Icon.ico + (None) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RemoteMicServerConsoleApplication_Icon.ico + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + RemoteMicServer_Icon.ico + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + + + DEBUG;$(DCC_Define) + false + true + + + true + + + Debug + + + true + + + RemoteMicServer_Icon.ico + + + true + + + true + true + Cfg_2 + true + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + RemoteMicServer_Icon.ico + + + + MainSource + + + +
ServerWebModule
+ TWebModule +
+ + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + RemoteMicServerConsoleApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + False + False + True + True + True + True + False + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + RemoteMicServerConsoleApplication + true + + + + + Assets\ + Logo44x44.png + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/RemoteMic/RemoteMicServerDaemon.dpr b/Demos/RemoteMic/RemoteMicServerDaemon.dpr new file mode 100644 index 00000000..cc63cab7 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerDaemon.dpr @@ -0,0 +1,21 @@ +program RemoteMicServerDaemon; + +{$APPTYPE CONSOLE} + +{$R *.res} + +uses + Classes, + SysUtils, + {$IFDEF LINUX} + MARS.Linux.Daemon in '..\..\Source\MARS.Linux.Daemon.pas', + {$ENDIF} + Server.Ignition in 'Server.Ignition.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +begin + {$IFDEF LINUX} + TMARSDaemon.Current.Name := 'RemoteMicServerDaemon'; + TMARSDaemon.Current.Start; + {$ENDIF} +end. diff --git a/Demos/RemoteMic/RemoteMicServerDaemon.dproj b/Demos/RemoteMic/RemoteMicServerDaemon.dproj new file mode 100644 index 00000000..c8dca5b0 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerDaemon.dproj @@ -0,0 +1,1161 @@ + + + {9D225C2C-24C2-48B4-8ABE-52C04AF576AE} + 18.8 + None + RemoteMicServerDaemon.dpr + True + Debug + Linux64 + 128 + Console + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Base + true + + + .\lib\$(Platform)\$(Config) + .\bin + false + false + false + false + false + RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage) + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + RemoteMicServerDaemon + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage);$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DataSnapServerMidas;FireDACADSDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;inetdb;emsedge;dbexpress;IndyCore;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;bindengine;FireDACOracleDriver;FireDACMySQLDriver;FireDACCommonODBC;DataSnapClient;IndySystem;FireDACDb2Driver;FireDACInfxDriver;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;rtl;DbxClientDriver;CustomIPTransport;bindcomp;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;dbrtl;FireDACMongoDBDriver;IndyProtocols;$(DCC_UsePackage) + /usr/bin/xterm -e "%debuggee%" + RemoteMicServer_Icon.ico + (None) + + + DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + true + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + Base + true + DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage);$(DCC_UsePackage) + true + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;CodeSiteExpressPkg;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;RadiantShapesFmx_Design;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(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 + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + /usr/bin/xterm -e "%debuggee%" + + + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Application + + + + RemoteMicServerDaemon.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + RemoteMicServerDaemon + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + False + False + False + False + False + True + False + False + False + False + + + 12 + + + + +
diff --git a/Demos/RemoteMic/RemoteMicServerFMXApplication.deployproj b/Demos/RemoteMic/RemoteMicServerFMXApplication.deployproj new file mode 100644 index 00000000..a082f2d3 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerFMXApplication.deployproj @@ -0,0 +1,132 @@ + + + + 12 + + + + + + iPhone5 + + + + + + + RemoteMicServerFMXApplication\ + RemoteMicServerFMXApplication.exe + ProjectOutput + 0 + + + True + True + + + + + RemoteMicServerFMXApplication.app\Assets\ + Logo44x44.png + UWP_DelphiLogo44 + 0 + + + True + + + RemoteMicServerFMXApplication.app\Contents\MacOS\ + libcgunwind.1.0.dylib + DependencyModule + 1 + + + True + + + RemoteMicServerFMXApplication.app\Contents\MacOS\ + RemoteMicServerFMXApplication.rsm + DebugSymbols + 1 + + + True + + + RemoteMicServerFMXApplication.app\..\ + RemoteMicServerFMXApplication.entitlements + ProjectOSXEntitlements + 1 + + + True + + + RemoteMicServerFMXApplication.app\Contents\Resources\ + RemoteMicServerFMXApplication.icns + ProjectOSXResource + 1 + + + True + + + RemoteMicServerFMXApplication.app\Contents\ + Info.plist + ProjectOSXInfoPList + 1 + + + True + + + RemoteMicServerFMXApplication.app\Contents\MacOS\ + libcgsqlite3.dylib + DependencyModule + 1 + + + True + + + RemoteMicServerFMXApplication.app\Assets\ + Logo150x150.png + UWP_DelphiLogo150 + 0 + + + True + + + RemoteMicServerFMXApplication.app\Contents\MacOS\ + RemoteMicServerFMXApplication + ProjectOutput + 1 + + + True + True + + + + + + RemoteMicServerFMXApplication.app\ + libPCRE.dylib + DependencyModule + 1 + + + True + + + RemoteMicServerFMXApplication.app\ + libcgunwind.1.0.dylib + DependencyModule + 1 + + + True + + + diff --git a/Demos/RemoteMic/RemoteMicServerFMXApplication.dpr b/Demos/RemoteMic/RemoteMicServerFMXApplication.dpr new file mode 100644 index 00000000..f2bcead0 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerFMXApplication.dpr @@ -0,0 +1,21 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program RemoteMicServerFMXApplication; + +uses + System.StartUpCopy, + FMX.Forms, + Server.FMX.Forms.Main in 'Server.FMX.Forms.Main.pas' {MainForm}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/RemoteMic/RemoteMicServerFMXApplication.dproj b/Demos/RemoteMic/RemoteMicServerFMXApplication.dproj new file mode 100644 index 00000000..f9609abc --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerFMXApplication.dproj @@ -0,0 +1,1376 @@ + + + {2DC28130-9EB1-48C8-9CA6-0F3CFD5D8E1D} + RemoteMicServerFMXApplication.dpr + True + Release + 38037 + Application + FMX + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + RemoteMicServerFMXApplication + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + .\bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + true + Base + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + RemoteMicServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + RemoteMicServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + true + true + Base + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + RemoteMicServer_Icon.ico + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + RemoteMicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + RemoteMicServerFMXApplication_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + Debug + + + true + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + + + true + Cfg_1 + true + true + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + RemoteMicServer_Icon.ico + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + true + + + Debug + + + true + + + $(BDS)\bin\delphi_PROJECTICNS.icns + true + + + true + true + Cfg_2 + true + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + RemoteMicServer_Icon.ico + PerMonitor + + + + MainSource + + +
MainForm
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + RemoteMicServerFMXApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + True + False + True + False + True + True + True + True + False + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + RemoteMicServerFMXApplication.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/RemoteMic/RemoteMicServerISAPI.dpr b/Demos/RemoteMic/RemoteMicServerISAPI.dpr new file mode 100644 index 00000000..b6ce1bde --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerISAPI.dpr @@ -0,0 +1,30 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +library RemoteMicServerISAPI; + +uses + Winapi.ActiveX, + System.Win.ComObj, + Web.WebBroker, + Web.Win.ISAPIApp, + Web.Win.ISAPIThreadPool, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +{$R *.res} + +exports + GetExtensionVersion, + HttpExtensionProc, + TerminateExtension; + +begin + CoInitFlags := COINIT_MULTITHREADED; + Application.Initialize; + Application.WebModuleClass := WebModuleClass; + Application.Run; +end. diff --git a/Demos/RemoteMic/RemoteMicServerISAPI.dproj b/Demos/RemoteMic/RemoteMicServerISAPI.dproj new file mode 100644 index 00000000..ae9a8091 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerISAPI.dproj @@ -0,0 +1,152 @@ + + + {9715EDC7-B308-42F7-B5EF-14BB4698B974} + RemoteMicServerISAPI.dpr + True + Release + 1 + Library + None + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + true + RemoteMicServerISAPI + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + .\bin + .\lib\$(Platform)\$(Config) + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + (None) + + + RemoteMicServerISAPI_Icon.ico + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + (None) + + + + MainSource + + + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + RemoteMicServerISAPI.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + True + False + + + 12 + + + +
diff --git a/Demos/RemoteMic/RemoteMicServerService.dpr b/Demos/RemoteMic/RemoteMicServerService.dpr new file mode 100644 index 00000000..2f8b5cc3 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerService.dpr @@ -0,0 +1,43 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program RemoteMicServerService; + +{$I MARS.inc} + +uses +{$ifdef DelphiXE3_UP} + Vcl.SvcMgr, +{$else} + SvcMgr, +{$endif} + + Server.Service in 'Server.Service.pas' {ServerService: TService}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +{$R *.RES} + +begin + // Windows 2003 Server requires StartServiceCtrlDispatcher to be + // called before CoRegisterClassObject, which can be called indirectly + // by Application.Initialize. TServiceApplication.DelayInitialize allows + // Application.Initialize to be called from TService.Main (after + // StartServiceCtrlDispatcher has been called). + // + // Delayed initialization of the Application object may affect + // events which then occur prior to initialization, such as + // TService.OnCreate. It is only recommended if the ServiceApplication + // registers a class object with OLE and is intended for use with + // Windows 2003 Server. + // + // Application.DelayInitialize := True; + // + if not Application.DelayInitialize or Application.Installing then + Application.Initialize; + Application.CreateForm(TServerService, ServerService); + Application.Run; +end. diff --git a/Demos/RemoteMic/RemoteMicServerService.dproj b/Demos/RemoteMic/RemoteMicServerService.dproj new file mode 100644 index 00000000..241a9531 --- /dev/null +++ b/Demos/RemoteMic/RemoteMicServerService.dproj @@ -0,0 +1,1135 @@ + + + {FEF5DF94-50B0-46F7-9F4B-6C355F140A2F} + RemoteMicServerService.dpr + True + Release + 3 + Application + None + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + RemoteMicServerService + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;Vcl;Winapi;$(DCC_Namespace) + .\lib\$(Platform)\$(Config) + .\bin + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + RemoteMicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + RemoteMicServer_Icon.ico + true + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + MARS.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + + MainSource + + +
ServerService
+ TService +
+ + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + RemoteMicServerService.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + True + True + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + Assets\ + Logo44x44.png + true + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + Assets\ + Logo44x44.png + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 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 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/RemoteMic/RemoteMicServer_Icon.ico b/Demos/RemoteMic/RemoteMicServer_Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0067d86ba25beefed52ee5181c1b8865b4e6c8ba GIT binary patch literal 67646 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 0 then + ATreeView.Items[0].Expand(True); + finally + ATreeView.Items.EndUpdate; + end; +end; + +procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); +begin + StopServerAction.Execute; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + PortNumberEdit.Text := IntToStr(TServerEngine.Default.Port); + RenderEngines(MainTreeView); + StartServerAction.Execute; +end; + +procedure TMainForm.PortNumberEditChange(Sender: TObject); +begin + TServerEngine.Default.Port := StrToInt(PortNumberEdit.Text); +end; + +procedure TMainForm.StartServerActionExecute(Sender: TObject); +begin + // http server implementation + FServer := TMARShttpServerIndy.Create(TServerEngine.Default); + try + FServer.DefaultPort := TServerEngine.Default.Port; + FServer.Active := True; + except + FServer.Free; + raise; + end; +end; + +procedure TMainForm.StartServerActionUpdate(Sender: TObject); +begin + StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); +end; + +procedure TMainForm.StopServerActionExecute(Sender: TObject); +begin + FServer.Active := False; + FreeAndNil(FServer); +end; + +procedure TMainForm.StopServerActionUpdate(Sender: TObject); +begin + StopServerAction.Enabled := Assigned(FServer) and (FServer.Active = True); +end; + +end. diff --git a/Demos/RemoteMic/Server.Ignition.pas b/Demos/RemoteMic/Server.Ignition.pas new file mode 100644 index 00000000..231c33cf --- /dev/null +++ b/Demos/RemoteMic/Server.Ignition.pas @@ -0,0 +1,148 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Ignition; + +{$I MARS.inc} + +interface + +uses + System.Classes, + System.SysUtils, + System.RTTI, + System.ZLib, + System.StrUtils, + MARS.Core.Engine; + +type + TServerEngine=class + private + class var FEngine: TMARSEngine; +{$IFDEF MARS_FIREDAC} + class var FAvailableConnectionDefs: TArray; +{$ENDIF} + public + class constructor CreateEngine; + class destructor DestroyEngine; + class property Default: TMARSEngine read FEngine; + end; + + +implementation + +uses + MARS.Core.Activation, MARS.Core.Activation.Interfaces + , MARS.Core.Application, MARS.Core.Utils, MARS.Utils.Parameters.IniFile + , MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyWriters + , MARS.Core.MessageBodyReaders, MARS.Data.MessageBodyWriters + {$IFDEF MARS_FIREDAC} , MARS.Data.FireDAC {$ENDIF} + {$IFDEF MSWINDOWS} , MARS.mORMotJWT.Token {$ELSE} , MARS.JOSEJWT.Token {$ENDIF} + , Server.Resources + ; + +{ TServerEngine } + +class constructor TServerEngine.CreateEngine; +begin + FEngine := TMARSEngine.Create; + try + // Engine configuration + FEngine.Parameters.LoadFromIniFile; + + // Application configuration + FEngine.AddApplication('DefaultApp', '/default', [ 'Server.Resources.*']); +{$IFDEF MARS_FIREDAC} + FAvailableConnectionDefs := TMARSFireDAC.LoadConnectionDefs(FEngine.Parameters, 'FireDAC'); +{$ENDIF} +{$REGION 'BeforeHandleRequest example'} +(* + FEngine.BeforeHandleRequest := + function (const AEngine: TMARSEngine; + const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; + var Handled: Boolean + ): Boolean + begin + Result := True; +{ + // skip favicon requests (browser) + if SameText(AURL.Document, 'favicon.ico') then + begin + Result := False; + Handled := True; + end; +} +{ + // Handle CORS and PreFlight + if SameText(ARequest.Method, 'OPTIONS') then + begin + Handled := True; + Result := False; + end; +} + end; +*) +{$ENDREGION} +{$REGION 'Global BeforeInvoke handler example'} +(* + // to execute something before each activation + TMARSActivation.RegisterBeforeInvoke( + procedure (const AActivation: IMARSActivation; out AIsAllowed: Boolean) + begin + + end + ); +*) +{$ENDREGION} +{$REGION 'Global AfterInvoke handler example'} + // Compression + if FEngine.Parameters.ByName('Compression.Enabled').AsBoolean then + TMARSActivation.RegisterAfterInvoke( + procedure (const AActivation: IMARSActivation) + var + LOutputStream: TBytesStream; + begin + if ContainsText(AActivation.Request.GetHeaderParamValue('Accept-Encoding'), 'gzip') then + begin + LOutputStream := TBytesStream.Create(nil); + try + ZipStream(AActivation.Response.ContentStream, LOutputStream, 15 + 16); + AActivation.Response.ContentStream.Free; + AActivation.Response.ContentStream := LOutputStream; + AActivation.Response.ContentEncoding := 'gzip'; + except + LOutputStream.Free; + raise; + end; + end; + end + ); +{$ENDREGION} +{$REGION 'Global InvokeError handler example'} +(* + // to execute something on error + TMARSActivation.RegisterInvokeError( + procedure (const AActivation: IMARSActivation; const AException: Exception; var AHandled: Boolean) + begin + + end + ); +*) +{$ENDREGION} + except + FreeAndNil(FEngine); + raise; + end; +end; + +class destructor TServerEngine.DestroyEngine; +begin +{$IFDEF MARS_FIREDAC} + TMARSFireDAC.CloseConnectionDefs(FAvailableConnectionDefs); +{$ENDIF} + FreeAndNil(FEngine); +end; + +end. diff --git a/Demos/RemoteMic/Server.Resources.pas b/Demos/RemoteMic/Server.Resources.pas new file mode 100644 index 00000000..56a573f6 --- /dev/null +++ b/Demos/RemoteMic/Server.Resources.pas @@ -0,0 +1,119 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Resources; + +interface + +uses + SysUtils, Classes + + , MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.JSON, MARS.Core.Response + , MARS.Core.URL + + , MARS.Core.Token.Resource //, MARS.Core.Token +; + +type + [Path('command')] + TCommandResource = class + private + type TApplication = (Skype, Teams, Zoom, Other, Unknown); + function GetCurrentApplication: TApplication; + protected + public + [GET, Path('/{param1}/{param2}'), Produces(TMediaType.TEXT_PLAIN)] + function Execute([PathParam] param1: Integer; [PathParam] param2: Integer): string; + end; + + [Path('token')] + TTokenResource = class(TMARSTokenResource) + end; + +implementation + +uses + Windows +, MARS.Core.Registry +, CodeSiteLogging +; + +{ TCommandResource } + +function TCommandResource.GetCurrentApplication: TApplication; +var + LText: array[0..255] of Char; + LTitle: string; + LHWND: HWND; +begin + Result := Unknown; + + LHWND := GetForegroundWindow; + GetWindowText(LHWND, @LText[0], SizeOf(LText)); + LTitle := LText; + + if LTitle.Contains('Microsoft Teams') then + Result := Teams + else if LTitle.Contains('Skype') then + Result := Skype + else if LTitle.Contains('Zoom') then + Result := Zoom + else + Result := Other; +end; + + +function TCommandResource.Execute(param1: Integer; param2: Integer): string; +begin + if (param1 + param2 < 2) then + begin + case GetCurrentApplication of + Teams: begin + Result := 'Teams: toggle mic'; + SetForegroundWindow(FindWindow(nil, PChar('Microsoft Teams'))); + + keybd_event(VK_LCONTROL, $9D,0 , 0); // Press Control + keybd_event(VK_LSHIFT, $AA,0 , 0); // Press Shift + keybd_event(Ord('M'), Ord('M'), 0 , 0); // Press M + + keybd_event(Ord('M'), Ord('M'), KEYEVENTF_KEYUP , 0); // Release M + keybd_event(VK_LSHIFT, $AA, KEYEVENTF_KEYUP, 0); // Release Shift + keybd_event(VK_LCONTROL, $9D, KEYEVENTF_KEYUP, 0); // Release Control + end; + Zoom: begin + Result := 'Zoom: toggle mic'; + SetForegroundWindow(FindWindow(nil, PChar('Zoom'))); + + keybd_event(VK_LCONTROL, $9D,0 , 0); // Press Control + keybd_event(VK_LSHIFT, $AA,0 , 0); // Press Shift + keybd_event(Ord('A'), Ord('A'), 0 , 0); // Press A + + keybd_event(Ord('A'), Ord('A'), KEYEVENTF_KEYUP , 0); // Release A + keybd_event(VK_LSHIFT, $AA, KEYEVENTF_KEYUP, 0); // Release Shift + keybd_event(VK_LCONTROL, $9D, KEYEVENTF_KEYUP, 0); // Release Control + end; + Skype: begin + Result := 'Skype: toggle mic'; + SetForegroundWindow(FindWindow(nil, PChar('Skype'))); + + keybd_event(VK_LCONTROL, $9D,0 , 0); // Press Control + keybd_event(Ord('M'), Ord('M'), 0 , 0); // Press M + + keybd_event(Ord('M'), Ord('M'), KEYEVENTF_KEYUP , 0); // Release M + keybd_event(VK_LSHIFT, $AA, KEYEVENTF_KEYUP, 0); // Release Shift + keybd_event(VK_LCONTROL, $9D, KEYEVENTF_KEYUP, 0); // Release Control + end; + else + Result := 'Other app'; + end; + + CodeSite.SendMsg(Result); + end; +end; + +initialization + TMARSResourceRegistry.Instance.RegisterResource; + TMARSResourceRegistry.Instance.RegisterResource; +end. diff --git a/Demos/RemoteMic/Server.Service.dfm b/Demos/RemoteMic/Server.Service.dfm new file mode 100644 index 00000000..52cc889a --- /dev/null +++ b/Demos/RemoteMic/Server.Service.dfm @@ -0,0 +1,10 @@ +object ServerService: TServerService + OldCreateOrder = False + OnCreate = ServiceCreate + OnDestroy = ServiceDestroy + DisplayName = 'RemoteMic Service' + OnStart = ServiceStart + OnStop = ServiceStop + Height = 150 + Width = 215 +end diff --git a/Demos/RemoteMic/Server.Service.pas b/Demos/RemoteMic/Server.Service.pas new file mode 100644 index 00000000..5f65d9ca --- /dev/null +++ b/Demos/RemoteMic/Server.Service.pas @@ -0,0 +1,126 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Service; + +{$I MARS.inc} + +interface + +uses +{$ifdef DelphiXE3_UP} + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics +, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs +//, IPPeerServer, IPPeerAPI +, IdHTTPWebBrokerBridge, Web.WebReq, Web.WebBroker +{$else} + Windows, Messages, SysUtils, Classes, Graphics +, Controls, SvcMgr, Dialogs +//, IPPeerServer, IPPeerAPI +, IdHTTPWebBrokerBridge, WebReq, WebBroker +{$endif} +, IdContext +; + +type + TServerService = class(TService) + procedure ServiceCreate(Sender: TObject); + procedure ServiceDestroy(Sender: TObject); + procedure ServiceStart(Sender: TService; var Started: Boolean); + procedure ServiceStop(Sender: TService; var Stopped: Boolean); + private + FServer: TIdHTTPWebBrokerBridge; + + procedure ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); virtual; + + public + function GetServiceController: TServiceController; override; + + const DEFAULT_PORT = 8080; + end; + +var + ServerService: TServerService; + +implementation + +{$R *.dfm} + +uses + IdSchedulerOfThreadPool +, Server.Ignition +, Server.WebModule +; + +procedure ServiceController(CtrlCode: DWord); stdcall; +begin + ServerService.Controller(CtrlCode); +end; + +function TServerService.GetServiceController: TServiceController; +begin + Result := ServiceController; +end; + +procedure TServerService.ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); +begin + // Allow JWT Bearer authentication's scheme + if SameText(AAuthType, 'Bearer') then + VHandled := True; +end; + +procedure TServerService.ServiceCreate(Sender: TObject); +var + LScheduler: TIdSchedulerOfThreadPool; +begin + Name := TServerEngine.Default.Parameters.ByNameText('ServiceName', Name).AsString; + DisplayName := TServerEngine.Default.Parameters.ByNameText('ServiceDisplayName', DisplayName).AsString; + + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + + FServer := TIdHTTPWebBrokerBridge.Create(nil); + try + FServer.DefaultPort := TServerEngine.Default.Port; + + LScheduler := TIdSchedulerOfThreadPool.Create(FServer); + try + LScheduler.PoolSize := TServerEngine.Default.ThreadPoolSize; + FServer.Scheduler := LScheduler; + FServer.MaxConnections := LScheduler.PoolSize; + FServer.OnParseAuthentication := ParseAuthenticationHandler; + except + FServer.Scheduler.Free; + FServer.Scheduler := nil; + raise; + end; + except + FServer.Free; + raise; + end; +end; + +procedure TServerService.ServiceDestroy(Sender: TObject); +begin + FreeAndNil(FServer); +end; + +procedure TServerService.ServiceStart(Sender: TService; var Started: Boolean); +begin + FServer.Active := True; + Started := FServer.Active; +end; + +procedure TServerService.ServiceStop(Sender: TService; var Stopped: Boolean); +begin + FServer.Active := False; + Stopped := not FServer.Active; +end; + +end. diff --git a/Demos/RemoteMic/Server.WebModule.dfm b/Demos/RemoteMic/Server.WebModule.dfm new file mode 100644 index 00000000..cfa79a10 --- /dev/null +++ b/Demos/RemoteMic/Server.WebModule.dfm @@ -0,0 +1,12 @@ +object ServerWebModule: TServerWebModule + OldCreateOrder = False + Actions = < + item + Default = True + Name = 'DefaultHandler' + PathInfo = '/' + OnAction = ServerWebModuleDefaultHandlerAction + end> + Height = 230 + Width = 415 +end diff --git a/Demos/RemoteMic/Server.WebModule.pas b/Demos/RemoteMic/Server.WebModule.pas new file mode 100644 index 00000000..2ad473c8 --- /dev/null +++ b/Demos/RemoteMic/Server.WebModule.pas @@ -0,0 +1,57 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.WebModule; + +{$I MARS.inc} + +interface + +uses System.SysUtils, System.Classes, Web.HTTPApp; + +type + TServerWebModule = class(TWebModule) + procedure ServerWebModuleDefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + private + { Private declarations } + public + { Public declarations } + end; + +var + WebModuleClass: TComponentClass = TServerWebModule; + +implementation + +{%CLASSGROUP 'System.Classes.TPersistent'} + +{$R *.dfm} + +uses + MARS.http.Server.Indy +, Server.Ignition; + +procedure TServerWebModule.ServerWebModuleDefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +begin + inherited; + + if not TServerEngine.Default.HandleRequest(TMARSWebRequest.Create(Request), TMARSWebResponse.Create(Response)) then + begin + Response.ContentType := 'application/json'; + Response.Content := + '{"success": false, "details": ' + + '{' + + '"error": "Request not found",' + + '"pathinfo": "' + Request.PathInfo + '"' + + '}' + + '}'; + end + else + Handled := True; +end; + +end. diff --git a/Demos/RemoteMic/ServerConst.pas b/Demos/RemoteMic/ServerConst.pas new file mode 100644 index 00000000..2ddaea07 --- /dev/null +++ b/Demos/RemoteMic/ServerConst.pas @@ -0,0 +1,42 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit ServerConst; + +interface + +resourcestring + sPortInUse = '- Error: Port %s already in use'; + sPortSet = '- Port set to %s'; + sServerRunning = '- The Server is already running'; + sStartingServer = '- Starting HTTP Server on port %d'; + sStoppingServer = '- Stopping Server'; + sServerStopped = '- Server Stopped'; + sServerNotRunning = '- The Server is not running'; + sInvalidCommand = '- Error: Invalid Command'; + sIndyVersion = '- Indy Version: '; + sActive = '- Active: '; + sPort = '- Port: '; + sSessionID = '- Session ID CookieName: '; + sCommands = 'Enter a Command: ' + slineBreak + + ' - "start" to start the server'+ slineBreak + + ' - "stop" to stop the server'+ slineBreak + + ' - "set port" to change the default port'+ slineBreak + + ' - "status" for Server status'+ slineBreak + + ' - "help" to show commands'+ slineBreak + + ' - "exit" to close the application'; + +const + cArrow = '->'; + cCommandStart = 'start'; + cCommandStop = 'stop'; + cCommandStatus = 'status'; + cCommandHelp = 'help'; + cCommandSetPort = 'set port'; + cCommandExit = 'exit'; + +implementation + +end. diff --git a/Demos/RemoteMic/bin/RemoteMicServerApplication.ini b/Demos/RemoteMic/bin/RemoteMicServerApplication.ini new file mode 100644 index 00000000..861a0b58 --- /dev/null +++ b/Demos/RemoteMic/bin/RemoteMicServerApplication.ini @@ -0,0 +1,4 @@ +[DefaultEngine] +BasePath= +ThreadPoolSize=100 +;Compression.Enabled=True diff --git a/Demos/UniDAC Basic/FMXClient.DataModules.Main.dfm b/Demos/UniDAC Basic/FMXClient.DataModules.Main.dfm new file mode 100644 index 00000000..650df259 --- /dev/null +++ b/Demos/UniDAC Basic/FMXClient.DataModules.Main.dfm @@ -0,0 +1,25 @@ +object MainDataModule: TMainDataModule + OldCreateOrder = False + Height = 411 + Width = 518 + object MARSApplication: TMARSClientApplication + DefaultMediaType = 'application/json' + DefaultContentType = 'application/json' + Client = MARSClient + Left = 88 + Top = 80 + end + object MARSClient: TMARSNetClient + MARSEngineURL = 'http://localhost:8080/rest' + ConnectTimeout = 60000 + ReadTimeout = 60000 + HttpClient.Asynchronous = False + HttpClient.ConnectionTimeout = 60000 + HttpClient.ResponseTimeout = 60000 + HttpClient.AllowCookies = True + HttpClient.HandleRedirects = True + HttpClient.UserAgent = 'Embarcadero URI Client/1.0' + Left = 88 + Top = 24 + end +end diff --git a/Demos/UniDAC Basic/FMXClient.DataModules.Main.pas b/Demos/UniDAC Basic/FMXClient.DataModules.Main.pas new file mode 100644 index 00000000..a4a66361 --- /dev/null +++ b/Demos/UniDAC Basic/FMXClient.DataModules.Main.pas @@ -0,0 +1,32 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit FMXClient.DataModules.Main; + +interface + +uses + System.SysUtils, System.Classes, MARS.Client.Application, + MARS.Client.Client, MARS.Client.Client.Net +; + +type + TMainDataModule = class(TDataModule) + MARSApplication: TMARSClientApplication; + MARSClient: TMARSNetClient; + private + public + end; + +var + MainDataModule: TMainDataModule; + +implementation + +{%CLASSGROUP 'FMX.Controls.TControl'} + +{$R *.dfm} + +end. diff --git a/Demos/UniDAC Basic/FMXClient.DataModules.Main.vlb b/Demos/UniDAC Basic/FMXClient.DataModules.Main.vlb new file mode 100644 index 00000000..95e7a6a2 --- /dev/null +++ b/Demos/UniDAC Basic/FMXClient.DataModules.Main.vlb @@ -0,0 +1,10 @@ +[MARSApplication] +Coordinates=150,53,96,33 + +[] +Coordinates=71,70,69,33 +Visible=False + +[MainForm.BindSourceDB1] +Coordinates=0,0,144,267 + diff --git a/Demos/UniDAC Basic/FMXClient.Forms.Main.fmx b/Demos/UniDAC Basic/FMXClient.Forms.Main.fmx new file mode 100644 index 00000000..9990bf0e --- /dev/null +++ b/Demos/UniDAC Basic/FMXClient.Forms.Main.fmx @@ -0,0 +1,27 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'MARS Template Client' + ClientHeight = 480 + ClientWidth = 640 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + DesignerMasterStyle = 0 + object TopToolBar: TToolBar + Size.Width = 640.000000000000000000 + Size.Height = 48.000000000000000000 + Size.PlatformDefault = False + TabOrder = 0 + object TitleLabel: TLabel + Align = Center + AutoSize = True + Size.Width = 121.000000000000000000 + Size.Height = 16.000000000000000000 + Size.PlatformDefault = False + StyleLookup = 'toollabel' + TextSettings.WordWrap = False + Text = 'MARS Template Client' + end + end +end diff --git a/Demos/UniDAC Basic/FMXClient.Forms.Main.pas b/Demos/UniDAC Basic/FMXClient.Forms.Main.pas new file mode 100644 index 00000000..1b939e4d --- /dev/null +++ b/Demos/UniDAC Basic/FMXClient.Forms.Main.pas @@ -0,0 +1,34 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit FMXClient.Forms.Main; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Layouts, FMX.Controls.Presentation; + +type + TMainForm = class(TForm) + TopToolBar: TToolBar; + TitleLabel: TLabel; + private + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.fmx} + +uses + FMXClient.DataModules.Main + ; + +end. diff --git a/Demos/UniDAC Basic/FMXClient.Forms.Main.vlb b/Demos/UniDAC Basic/FMXClient.Forms.Main.vlb new file mode 100644 index 00000000..b439f67e --- /dev/null +++ b/Demos/UniDAC Basic/FMXClient.Forms.Main.vlb @@ -0,0 +1,26 @@ +[TopToolBar] +Coordinates=317,78,82,36 + +[MainDataModule.ItemsQueryDataSet] +Coordinates=10,10,228,212 + +[] +Coordinates=145,78,71,36 +Visible=True + +[TitleLabel] +Coordinates=236,78,71,58 + +[MainDataModule.] +Coordinates=472,428,215,249 +Visible=False + +[MainDataModule.EmployeeQuery1] +Visible=False + +[MainDataModule.EmployeeQueryDataSet] +Coordinates=100,10,253,58 + +[MainDataModule.CountryByName1] +Coordinates=10,78,216,58 + diff --git a/Demos/UniDAC Basic/Server.FMX.Forms.Main.fmx b/Demos/UniDAC Basic/Server.FMX.Forms.Main.fmx new file mode 100644 index 00000000..fdadb145 --- /dev/null +++ b/Demos/UniDAC Basic/Server.FMX.Forms.Main.fmx @@ -0,0 +1,66 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'MARS-Curiosity Template Server FMX' + ClientHeight = 480 + ClientWidth = 640 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnClose = FormClose + DesignerMasterStyle = 0 + object Layout1: TLayout + Align = Top + Size.Width = 640.000000000000000000 + Size.Height = 121.000000000000000000 + Size.PlatformDefault = False + TabOrder = 1 + object PortNumberEdit: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 0 + Text = '8080' + Position.X = 109.000000000000000000 + Position.Y = 22.000000000000000000 + OnChange = PortNumberEditChange + end + object Label1: TLabel + Position.X = 16.000000000000000000 + Position.Y = 24.000000000000000000 + Size.Width = 89.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = 'Port number:' + end + object StartButton: TButton + Action = StartServerAction + Enabled = True + ImageIndex = -1 + Position.X = 16.000000000000000000 + Position.Y = 64.000000000000000000 + TabOrder = 2 + end + object StopButton: TButton + Action = StopServerAction + Enabled = True + ImageIndex = -1 + Position.X = 104.000000000000000000 + Position.Y = 64.000000000000000000 + TabOrder = 3 + end + end + object MainActionList: TActionList + Left = 384 + Top = 24 + object StartServerAction: TAction + Text = 'Start Server' + OnExecute = StartServerActionExecute + OnUpdate = StartServerActionUpdate + end + object StopServerAction: TAction + Text = 'Stop Server' + OnExecute = StopServerActionExecute + OnUpdate = StopServerActionUpdate + end + end +end diff --git a/Demos/UniDAC Basic/Server.FMX.Forms.Main.pas b/Demos/UniDAC Basic/Server.FMX.Forms.Main.pas new file mode 100644 index 00000000..36c1f348 --- /dev/null +++ b/Demos/UniDAC Basic/Server.FMX.Forms.Main.pas @@ -0,0 +1,97 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.FMX.Forms.Main; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Controls.Presentation, FMX.Edit, FMX.Layouts, System.Actions, FMX.ActnList + , MARS.http.Server.Indy +; + +type + TMainForm = class(TForm) + MainActionList: TActionList; + StartServerAction: TAction; + StopServerAction: TAction; + Layout1: TLayout; + PortNumberEdit: TEdit; + Label1: TLabel; + StartButton: TButton; + StopButton: TButton; + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure FormCreate(Sender: TObject); + procedure StartServerActionExecute(Sender: TObject); + procedure StopServerActionExecute(Sender: TObject); + procedure StartServerActionUpdate(Sender: TObject); + procedure StopServerActionUpdate(Sender: TObject); + procedure PortNumberEditChange(Sender: TObject); + private + FServer: TMARShttpServerIndy; + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.fmx} + +uses + Web.HttpApp + , MARS.Core.URL, MARS.Core.Engine + , Server.Ignition; + +procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); +begin + StopServerAction.Execute; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + PortNumberEdit.Text := TServerEngine.Default.Port.ToString; + + StartServerAction.Execute; +end; + +procedure TMainForm.PortNumberEditChange(Sender: TObject); +begin + TServerEngine.Default.Port := StrToInt(PortNumberEdit.Text); +end; + +procedure TMainForm.StartServerActionExecute(Sender: TObject); +begin + // http server implementation + FServer := TMARShttpServerIndy.Create(TServerEngine.Default); + try + FServer.DefaultPort := TServerEngine.Default.Port; + FServer.Active := True; + except + FServer.Free; + raise; + end; +end; + +procedure TMainForm.StartServerActionUpdate(Sender: TObject); +begin + StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); +end; + +procedure TMainForm.StopServerActionExecute(Sender: TObject); +begin + FServer.Active := False; + FreeAndNil(FServer); +end; + +procedure TMainForm.StopServerActionUpdate(Sender: TObject); +begin + StopServerAction.Enabled := Assigned(FServer) and (FServer.Active = True); +end; + +end. diff --git a/Demos/UniDAC Basic/Server.Forms.Main.dfm b/Demos/UniDAC Basic/Server.Forms.Main.dfm new file mode 100644 index 00000000..5220be6e --- /dev/null +++ b/Demos/UniDAC Basic/Server.Forms.Main.dfm @@ -0,0 +1,83 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'UniDACBasic Server' + ClientHeight = 201 + ClientWidth = 464 + Color = clBtnFace + Constraints.MinHeight = 240 + Constraints.MinWidth = 480 + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + OnClose = FormClose + OnCreate = FormCreate + PixelsPerInch = 96 + TextHeight = 13 + object TopPanel: TPanel + Left = 0 + Top = 0 + Width = 464 + Height = 73 + Align = alTop + BevelOuter = bvNone + TabOrder = 0 + object Label1: TLabel + Left = 28 + Top = 17 + Width = 63 + Height = 13 + Caption = 'Port number:' + end + object StartButton: TButton + Left = 16 + Top = 41 + Width = 75 + Height = 25 + Action = StartServerAction + TabOrder = 0 + end + object StopButton: TButton + Left = 104 + Top = 41 + Width = 75 + Height = 25 + Action = StopServerAction + TabOrder = 1 + end + object PortNumberEdit: TEdit + Left = 97 + Top = 14 + Width = 82 + Height = 21 + TabOrder = 2 + OnChange = PortNumberEditChange + end + end + object MainTreeView: TTreeView + Left = 0 + Top = 73 + Width = 464 + Height = 128 + Align = alClient + Indent = 19 + TabOrder = 1 + end + object MainActionList: TActionList + Left = 384 + Top = 24 + object StartServerAction: TAction + Caption = 'Start Server' + OnExecute = StartServerActionExecute + OnUpdate = StartServerActionUpdate + end + object StopServerAction: TAction + Caption = 'Stop Server' + OnExecute = StopServerActionExecute + OnUpdate = StopServerActionUpdate + end + end +end diff --git a/Demos/UniDAC Basic/Server.Forms.Main.pas b/Demos/UniDAC Basic/Server.Forms.Main.pas new file mode 100644 index 00000000..7bf3579a --- /dev/null +++ b/Demos/UniDAC Basic/Server.Forms.Main.pas @@ -0,0 +1,148 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Forms.Main; + +{$I MARS.inc} + +interface + +uses Classes, SysUtils, Forms, ActnList, ComCtrls, StdCtrls, Controls, ExtCtrls + , System.Actions + , MARS.http.Server.Indy +; + +type + TMainForm = class(TForm) + MainActionList: TActionList; + StartServerAction: TAction; + StopServerAction: TAction; + TopPanel: TPanel; + Label1: TLabel; + StartButton: TButton; + StopButton: TButton; + PortNumberEdit: TEdit; + MainTreeView: TTreeView; + procedure StartServerActionExecute(Sender: TObject); + procedure StartServerActionUpdate(Sender: TObject); + procedure StopServerActionExecute(Sender: TObject); + procedure StopServerActionUpdate(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure PortNumberEditChange(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + private + FServer: TMARShttpServerIndy; + protected + procedure RenderEngines(const ATreeView: TTreeView); + public + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.dfm} + +uses + StrUtils, Web.HttpApp + , MARS.Core.URL, MARS.Core.Engine, MARS.Core.Application, MARS.Core.Registry + , MARS.Core.Registry.Utils + , Server.Ignition +; + +procedure TMainForm.RenderEngines(const ATreeView: TTreeView); +begin + + ATreeview.Items.BeginUpdate; + try + ATreeview.Items.Clear; + TMARSEngineRegistry.Instance.EnumerateEngines( + procedure (AName: string; AEngine: TMARSEngine) + var + LEngineItem: TTreeNode; + begin + LEngineItem := ATreeview.Items.AddChild(nil + , AName + ' [ :' + AEngine.Port.ToString + AEngine.BasePath + ']' + ); + + AEngine.EnumerateApplications( + procedure (AName: string; AApplication: TMARSApplication) + var + LApplicationItem: TTreeNode; + begin + LApplicationItem := ATreeview.Items.AddChild(LEngineItem + , AApplication.Name + ' [' + AApplication.BasePath + ']' + ); + + AApplication.EnumerateResources( + procedure (AName: string; AInfo: TMARSConstructorInfo) + begin + ATreeview.Items.AddChild( + LApplicationItem + , AInfo.TypeTClass.ClassName + ' [' + AName + ']' + ); + + end + ); + end + ); + end + ); + + if ATreeView.Items.Count > 0 then + ATreeView.Items[0].Expand(True); + finally + ATreeView.Items.EndUpdate; + end; +end; + +procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); +begin + StopServerAction.Execute; +end; + +procedure TMainForm.FormCreate(Sender: TObject); +begin + PortNumberEdit.Text := IntToStr(TServerEngine.Default.Port); + RenderEngines(MainTreeView); + StartServerAction.Execute; +end; + +procedure TMainForm.PortNumberEditChange(Sender: TObject); +begin + TServerEngine.Default.Port := StrToInt(PortNumberEdit.Text); +end; + +procedure TMainForm.StartServerActionExecute(Sender: TObject); +begin + // http server implementation + FServer := TMARShttpServerIndy.Create(TServerEngine.Default); + try + FServer.DefaultPort := TServerEngine.Default.Port; + FServer.Active := True; + except + FServer.Free; + raise; + end; +end; + +procedure TMainForm.StartServerActionUpdate(Sender: TObject); +begin + StartServerAction.Enabled := (FServer = nil) or (FServer.Active = False); +end; + +procedure TMainForm.StopServerActionExecute(Sender: TObject); +begin + FServer.Active := False; + FreeAndNil(FServer); +end; + +procedure TMainForm.StopServerActionUpdate(Sender: TObject); +begin + StopServerAction.Enabled := Assigned(FServer) and (FServer.Active = True); +end; + +end. diff --git a/Demos/UniDAC Basic/Server.Ignition.pas b/Demos/UniDAC Basic/Server.Ignition.pas new file mode 100644 index 00000000..9565ce8c --- /dev/null +++ b/Demos/UniDAC Basic/Server.Ignition.pas @@ -0,0 +1,149 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Ignition; + +{$I MARS.inc} + +interface + +uses + Classes, SysUtils, Rtti + , MARS.Core.Engine +; + +type + TServerEngine=class + private + class var FEngine: TMARSEngine; +{$IF Defined(MARS_FIREDAC) or Defined(MARS_UNIDAC)} + class var FAvailableConnectionDefs: TArray; +{$ENDIF} + public + class constructor CreateEngine; + class destructor DestroyEngine; + class property Default: TMARSEngine read FEngine; + end; + + +implementation + +uses + MARS.Core.Activation, MARS.Core.Activation.Interfaces + , MARS.Core.Application, MARS.Core.Utils, MARS.Utils.Parameters.IniFile + , MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyWriters + , MARS.Core.MessageBodyReaders, MARS.Data.MessageBodyWriters + {$IFDEF MARS_FIREDAC} + , MARS.Data.FireDAC + , FireDAC.Phys.FB + {$ENDIF} + {$IFDEF MARS_UNIDAC} + , MARS.Data.UniDAC + , InterBaseUniProvider // Interbase & FirebirdSQL + , SQLiteUniProvider // SQLite + {$ENDIF} + {$IFDEF MSWINDOWS} , MARS.mORMotJWT.Token {$ELSE} , MARS.JOSEJWT.Token {$ENDIF} + , Server.Resources + + ; + +{ TServerEngine } + +class constructor TServerEngine.CreateEngine; +begin + FEngine := TMARSEngine.Create; + try + // Engine configuration + FEngine.Parameters.LoadFromIniFile; + + // Application configuration + FEngine.AddApplication('DefaultApp', '/default', [ 'Server.Resources.*']); +{$IFDEF MARS_FIREDAC} + FAvailableConnectionDefs := TMARSFireDAC.LoadConnectionDefs(FEngine.Parameters, 'FireDAC'); +{$ENDIF} +{$IFDEF MARS_UNIDAC} + FAvailableConnectionDefs := TMARSUniDAC.LoadConnectionDefs(FEngine.Parameters, 'UniDAC'); +{$ENDIF} + +{$REGION 'BeforeHandleRequest example'} +(* + FEngine.BeforeHandleRequest := + function (const AEngine: TMARSEngine; + const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; + var Handled: Boolean + ): Boolean + begin + Result := True; +{ + // skip favicon requests (browser) + if SameText(AURL.Document, 'favicon.ico') then + begin + Result := False; + Handled := True; + end; +} +{ + // Handle CORS and PreFlight + if SameText(ARequest.Method, 'OPTIONS') then + begin + Handled := True; + Result := False; + end; +} + end; +*) +{$ENDREGION} +{$REGION 'Global BeforeInvoke handler example'} +(* + // to execute something before each activation + TMARSActivation.RegisterBeforeInvoke( + procedure (const AActivation: IMARSActivation; out AIsAllowed: Boolean) + begin + + end + ); +*) +{$ENDREGION} +{$REGION 'Global AfterInvoke handler example'} +(* + // to execute something after each activation + TMARSActivation.RegisterAfterInvoke( + procedure (const AActivation: IMARSActivation) + begin + + end + ); +*) +{$ENDREGION} +{$REGION 'Global InvokeError handler example'} +(* + // to execute something on error + TMARSActivation.RegisterInvokeError( + procedure (const AActivation: IMARSActivation; const AException: Exception; var AHandled: Boolean) + begin + + end + ); +*) +{$ENDREGION} + except + FreeAndNil(FEngine); + raise; + end; +end; + +class destructor TServerEngine.DestroyEngine; +begin +{$IFDEF MARS_FIREDAC} + TMARSFireDAC.CloseConnectionDefs(FAvailableConnectionDefs); +{$ENDIF} +{$IFDEF MARS_UNIDAC} + TMARSUniDAC.CloseConnectionDefs(FAvailableConnectionDefs); +{$ENDIF} + + FreeAndNil(FEngine); +end; + +end. diff --git a/Demos/UniDAC Basic/Server.Resources.pas b/Demos/UniDAC Basic/Server.Resources.pas new file mode 100644 index 00000000..b8ba835b --- /dev/null +++ b/Demos/UniDAC Basic/Server.Resources.pas @@ -0,0 +1,107 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Resources; + +interface + +uses + SysUtils, Classes, Data.DB + + , MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.JSON, MARS.Core.Response + , MARS.Core.URL + + , MARS.Core.Token.Resource //, MARS.Core.Token + + // UniDAC + , Uni, VirtualTable + , MARS.Data.UniDAC, MARS.Data.UniDAC.Utils + + , FireDAC.Comp.Client, MARS.Data.FireDAC +; + +type + [ Path('helloworld') + , Produces(TMediaType.APPLICATION_XML) + , Produces(TMediaType.APPLICATION_JSON) + , Produces(APPLICATION_JSON_UniDAC) + , Produces(TMediaType.APPLICATION_OCTET_STREAM) + ] + THelloWorldResource = class + protected + [Context] UD: TMARSUniDAC; + [Context] FD: TMARSFireDAC; + [Context, Connection('MY_SQLITE')] UDSQLite: TMARSUniDAC; + public + [GET, Path('query/{tablename}')] + function GetEmployee: TUniQuery; + [GET, Path('queryFD/{tablename}')] + function GetEmployeeFD: TFDQuery; + + [GET, Path('querySQLite/{tablename}')] + function GetTable1: TUniQuery; + [GET, Path('queries')] + function GetQueries: TArray; + + [GET, Path('virtualtable')] + function GetVirtualTable: TVirtualTable; + end; + + [Path('token')] + TTokenResource = class(TMARSTokenResource) + end; + +implementation + +uses + MARS.Core.Registry +; + +{ THelloWorldResource } + +function THelloWorldResource.GetEmployee: TUniQuery; +begin + Result := UD.Query('select * from &PathParam_tablename'); +end; + + +function THelloWorldResource.GetEmployeeFD: TFDQuery; +begin + Result := FD.Query('select * from &PathParam_tablename'); +end; + +function THelloWorldResource.GetQueries: TArray; +begin + Result := [ + UD.SetName(UD.Query('select * from EMPLOYEE'), 'Employees') + , UDSQLite.SetName( UDSQLite.Query('select * from MYTABLE1'), 'MyTable1') + ]; +end; + +function THelloWorldResource.GetTable1: TUniQuery; +begin + Result := UDSQLite.Query('select * from &PathParam_tablename'); +end; + +function THelloWorldResource.GetVirtualTable: TVirtualTable; +begin + Result := TVirtualTable.Create(nil); + try + Result.AddField('Lastname', ftString, 100); + Result.AddField('Firstname', ftString, 100); + Result.AddField('DateOfBirth', ftDate); + + Result.Active := True; + Result.AppendRecord(['Magni', 'Andrea', EncodeDate(1982, 05, 24)]); + Result.AppendRecord(['K', 'Ertan', EncodeDate(2000, 01, 01)]); + except + FreeAndNil(Result); + end; +end; + +initialization + TMARSResourceRegistry.Instance.RegisterResource; + TMARSResourceRegistry.Instance.RegisterResource; +end. diff --git a/Demos/UniDAC Basic/Server.Service.dfm b/Demos/UniDAC Basic/Server.Service.dfm new file mode 100644 index 00000000..012a74e0 --- /dev/null +++ b/Demos/UniDAC Basic/Server.Service.dfm @@ -0,0 +1,10 @@ +object ServerService: TServerService + OldCreateOrder = False + OnCreate = ServiceCreate + OnDestroy = ServiceDestroy + DisplayName = 'UniDACBasic Service' + OnStart = ServiceStart + OnStop = ServiceStop + Height = 150 + Width = 215 +end diff --git a/Demos/UniDAC Basic/Server.Service.pas b/Demos/UniDAC Basic/Server.Service.pas new file mode 100644 index 00000000..d47167bd --- /dev/null +++ b/Demos/UniDAC Basic/Server.Service.pas @@ -0,0 +1,123 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.Service; + +{$I MARS.inc} + +interface + +uses +{$ifdef DelphiXE3_UP} + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics +, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs +//, IPPeerServer, IPPeerAPI +, IdHTTPWebBrokerBridge, Web.WebReq, Web.WebBroker +{$else} + Windows, Messages, SysUtils, Classes, Graphics +, Controls, SvcMgr, Dialogs +//, IPPeerServer, IPPeerAPI +, IdHTTPWebBrokerBridge, WebReq, WebBroker +{$endif} +, IdContext +; + +type + TServerService = class(TService) + procedure ServiceCreate(Sender: TObject); + procedure ServiceDestroy(Sender: TObject); + procedure ServiceStart(Sender: TService; var Started: Boolean); + procedure ServiceStop(Sender: TService; var Stopped: Boolean); + private + FServer: TIdHTTPWebBrokerBridge; + + procedure ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); virtual; + + public + function GetServiceController: TServiceController; override; + + const DEFAULT_PORT = 8080; + end; + +var + ServerService: TServerService; + +implementation + +{$R *.dfm} + +uses + IdSchedulerOfThreadPool +, Server.Ignition +, Server.WebModule +; + +procedure ServiceController(CtrlCode: DWord); stdcall; +begin + ServerService.Controller(CtrlCode); +end; + +function TServerService.GetServiceController: TServiceController; +begin + Result := ServiceController; +end; + +procedure TServerService.ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); +begin + // Allow JWT Bearer authentication's scheme + if SameText(AAuthType, 'Bearer') then + VHandled := True; +end; + +procedure TServerService.ServiceCreate(Sender: TObject); +var + LScheduler: TIdSchedulerOfThreadPool; +begin + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + + FServer := TIdHTTPWebBrokerBridge.Create(nil); + try + FServer.DefaultPort := TServerEngine.Default.Port; + + LScheduler := TIdSchedulerOfThreadPool.Create(FServer); + try + LScheduler.PoolSize := TServerEngine.Default.ThreadPoolSize; + FServer.Scheduler := LScheduler; + FServer.MaxConnections := LScheduler.PoolSize; + FServer.OnParseAuthentication := ParseAuthenticationHandler; + except + FServer.Scheduler.Free; + FServer.Scheduler := nil; + raise; + end; + except + FServer.Free; + raise; + end; +end; + +procedure TServerService.ServiceDestroy(Sender: TObject); +begin + FreeAndNil(FServer); +end; + +procedure TServerService.ServiceStart(Sender: TService; var Started: Boolean); +begin + FServer.Active := True; + Started := FServer.Active; +end; + +procedure TServerService.ServiceStop(Sender: TService; var Stopped: Boolean); +begin + FServer.Active := False; + Stopped := not FServer.Active; +end; + +end. diff --git a/Demos/UniDAC Basic/Server.WebModule.dfm b/Demos/UniDAC Basic/Server.WebModule.dfm new file mode 100644 index 00000000..cfa79a10 --- /dev/null +++ b/Demos/UniDAC Basic/Server.WebModule.dfm @@ -0,0 +1,12 @@ +object ServerWebModule: TServerWebModule + OldCreateOrder = False + Actions = < + item + Default = True + Name = 'DefaultHandler' + PathInfo = '/' + OnAction = ServerWebModuleDefaultHandlerAction + end> + Height = 230 + Width = 415 +end diff --git a/Demos/UniDAC Basic/Server.WebModule.pas b/Demos/UniDAC Basic/Server.WebModule.pas new file mode 100644 index 00000000..2ad473c8 --- /dev/null +++ b/Demos/UniDAC Basic/Server.WebModule.pas @@ -0,0 +1,57 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit Server.WebModule; + +{$I MARS.inc} + +interface + +uses System.SysUtils, System.Classes, Web.HTTPApp; + +type + TServerWebModule = class(TWebModule) + procedure ServerWebModuleDefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + private + { Private declarations } + public + { Public declarations } + end; + +var + WebModuleClass: TComponentClass = TServerWebModule; + +implementation + +{%CLASSGROUP 'System.Classes.TPersistent'} + +{$R *.dfm} + +uses + MARS.http.Server.Indy +, Server.Ignition; + +procedure TServerWebModule.ServerWebModuleDefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +begin + inherited; + + if not TServerEngine.Default.HandleRequest(TMARSWebRequest.Create(Request), TMARSWebResponse.Create(Response)) then + begin + Response.ContentType := 'application/json'; + Response.Content := + '{"success": false, "details": ' + + '{' + + '"error": "Request not found",' + + '"pathinfo": "' + Request.PathInfo + '"' + + '}' + + '}'; + end + else + Handled := True; +end; + +end. diff --git a/Demos/UniDAC Basic/ServerConst.pas b/Demos/UniDAC Basic/ServerConst.pas new file mode 100644 index 00000000..2ddaea07 --- /dev/null +++ b/Demos/UniDAC Basic/ServerConst.pas @@ -0,0 +1,42 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +unit ServerConst; + +interface + +resourcestring + sPortInUse = '- Error: Port %s already in use'; + sPortSet = '- Port set to %s'; + sServerRunning = '- The Server is already running'; + sStartingServer = '- Starting HTTP Server on port %d'; + sStoppingServer = '- Stopping Server'; + sServerStopped = '- Server Stopped'; + sServerNotRunning = '- The Server is not running'; + sInvalidCommand = '- Error: Invalid Command'; + sIndyVersion = '- Indy Version: '; + sActive = '- Active: '; + sPort = '- Port: '; + sSessionID = '- Session ID CookieName: '; + sCommands = 'Enter a Command: ' + slineBreak + + ' - "start" to start the server'+ slineBreak + + ' - "stop" to stop the server'+ slineBreak + + ' - "set port" to change the default port'+ slineBreak + + ' - "status" for Server status'+ slineBreak + + ' - "help" to show commands'+ slineBreak + + ' - "exit" to close the application'; + +const + cArrow = '->'; + cCommandStart = 'start'; + cCommandStop = 'stop'; + cCommandStatus = 'status'; + cCommandHelp = 'help'; + cCommandSetPort = 'set port'; + cCommandExit = 'exit'; + +implementation + +end. diff --git a/Demos/UniDAC Basic/UniDACBasicClient.dpr b/Demos/UniDAC Basic/UniDACBasicClient.dpr new file mode 100644 index 00000000..ec85dd83 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicClient.dpr @@ -0,0 +1,21 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) + program UniDACBasicClient; + +uses + System.StartUpCopy, + FMX.Forms, + FMXClient.Forms.Main in 'FMXClient.Forms.Main.pas' {MainForm}, + FMXClient.DataModules.Main in 'FMXClient.DataModules.Main.pas' {MainDataModule: TDataModule}; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainDataModule, MainDataModule); + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/UniDAC Basic/UniDACBasicClient.dproj b/Demos/UniDAC Basic/UniDACBasicClient.dproj new file mode 100644 index 00000000..bf43c19d --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicClient.dproj @@ -0,0 +1,1259 @@ + + + {B6B860BA-E3A5-48E9-8EC6-7FE516AE7FC4} + UniDACBasicClient.dpr + True + Debug + 1169 + Application + FMX + 18.7 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + UniDACBasicClient + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + .\bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + + + $(BDS)\bin\delphi_PROJECTICNS.icns + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + true + Base + true + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + UniDACBasicServer_Icon.ico + + + $(BDS)\bin\default_app.manifest + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + true + + + Debug + + + true + + + $(BDS)\bin\delphi_PROJECTICNS.icns + + + true + true + Cfg_2 + true + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + UniDACBasicServer_Icon.ico + PerMonitor + + + + MainSource + + +
MainForm
+
+ +
MainDataModule
+ TDataModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + UniDACBasicClient.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + False + True + False + True + False + False + True + False + + + + + true + + + + + ic_launcher.png + true + + + + + ic_launcher.png + true + + + + + libUniDACBasicClient.so + true + + + + + ic_launcher.png + true + + + + + true + + + + + true + + + + + splash_image.png + true + + + + + true + + + + + splash_image.png + true + + + + + splash_image.png + true + + + + + splash_image.png + true + + + + + true + + + + + classes.dex + true + + + + + true + + + + + ic_launcher.png + true + + + + + libUniDACBasicClient.so + true + + + + + ic_launcher.png + true + + + + + true + + + + + true + + + + + libUniDACBasicClient.so + true + + + + + true + + + + + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\values-v21 + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicProjectGroup.groupproj b/Demos/UniDAC Basic/UniDACBasicProjectGroup.groupproj new file mode 100644 index 00000000..743e65bd --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicProjectGroup.groupproj @@ -0,0 +1,120 @@ + + + {6E23DEFF-F737-42C3-B8AD-2549B8F67C93} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demos/UniDAC Basic/UniDACBasicServerApacheModule.dpr b/Demos/UniDAC Basic/UniDACBasicServerApacheModule.dpr new file mode 100644 index 00000000..55815590 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerApacheModule.dpr @@ -0,0 +1,54 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +library UniDACBasicServerApacheModule; + +uses + {$IFDEF MSWINDOWS} + Winapi.ActiveX, System.Win.ComObj, + {$ENDIF } + Web.WebBroker, + Web.ApacheApp, + Web.HTTPD24Impl, + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas'; + +{$R *.res} + +// httpd.conf entries: +// +(* + LoadModule marstemplate_module modules/mod_marstemplate.dll + + + SetHandler mod_marstemplate-handler + +*) +// +// These entries assume that the output directory for this project is the apache/modules directory. +// +// httpd.conf entries should be different if the project is changed in these ways: +// 1. The TApacheModuleData variable name is changed. +// 2. The project is renamed. +// 3. The output directory is not the apache/modules directory. +// 4. The dynamic library extension depends on a platform. Use .dll on Windows and .so on Linux. +// + +// Declare exported variable so that Apache can access this module. +var + GModuleData: TApacheModuleData; +exports + GModuleData name 'marstemplate_module'; + +begin +{$IFDEF MSWINDOWS} + CoInitFlags := COINIT_MULTITHREADED; +{$ENDIF} + Web.ApacheApp.InitApplication(@GModuleData); + Application.Initialize; + Application.WebModuleClass := WebModuleClass; + Application.Run; +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerApacheModule.dproj b/Demos/UniDAC Basic/UniDACBasicServerApacheModule.dproj new file mode 100644 index 00000000..4489e7c8 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerApacheModule.dproj @@ -0,0 +1,856 @@ + + + {0D982E91-6C92-4321-9078-458448B01536} + 18.7 + None + UniDACBasicServerApacheModule.dpr + True + Release + Win32 + 129 + Library + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\lib\$(Platform)\$(Config) + .\bin + false + false + false + false + false + true + RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage) + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + UniDACBasicServerApacheModule + + + DataSnapServerMidas;FireDACADSDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;inetdb;emsedge;dbexpress;IndyCore;dsnap;DataSnapCommon;DataSnapConnectors;bindengine;FireDACOracleDriver;FireDACMySQLDriver;FireDACCommonODBC;DataSnapClient;IndySystem;FireDACDb2Driver;FireDACInfxDriver;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;rtl;DbxClientDriver;CustomIPTransport;bindcomp;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;dbrtl;FireDACMongoDBDriver;IndyProtocols;$(DCC_UsePackage) + true + /usr/bin/xterm -e "%debuggee%" + (None) + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;CodeSiteExpressPkg;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + 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 + (None) + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + true + 1033 + (None) + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + +
ServerWebModule
+ dfm + TWebModule +
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Library + + + + UniDACBasicServerApacheModule.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + UniDACBasicServerApacheModule.dll + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + libUniDACBasicServerApacheModule.so + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\values-v21 + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + True + True + False + + + 12 + + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServerApplication.dpr b/Demos/UniDAC Basic/UniDACBasicServerApplication.dpr new file mode 100644 index 00000000..07a8089c --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerApplication.dpr @@ -0,0 +1,23 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) + program UniDACBasicServerApplication; + +uses + Forms, + Server.Forms.Main in 'Server.Forms.Main.pas' {MainForm}, + Server.Resources in 'Server.Resources.pas', + Server.Ignition in 'Server.Ignition.pas'; + +{$R *.res} + +begin + ReportMemoryLeaksOnShutdown := True; + + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerApplication.dproj b/Demos/UniDAC Basic/UniDACBasicServerApplication.dproj new file mode 100644 index 00000000..40028b56 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerApplication.dproj @@ -0,0 +1,190 @@ + + + {0A5E1DDC-90B4-4B41-A7DC-2B0FC45D4349} + UniDACBasicServerApplication.dpr + True + Debug + 3 + Application + VCL + 18.7 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + UniDACBasicServerApplication + Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + .\bin + .\lib\$(Platform)\$(Config) + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + UniDACBasicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + UniDACBasicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + UniDACBasicServer_Icon.ico + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + UniDACBasicServer_Icon.ico + PerMonitor + + + true + PerMonitor + + + + MainSource + + +
MainForm
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + UniDACBasicServerApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + True + True + + + 12 + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dpr b/Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dpr new file mode 100644 index 00000000..882167f5 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dpr @@ -0,0 +1,201 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program UniDACBasicServerConsoleApplication; +{$APPTYPE CONSOLE} + +{$I MARS.inc} + +uses +{$ifdef DelphiXE3_UP} + System.SysUtils, + System.Types, +// IPPeerServer, IPPeerAPI, + IdHTTPWebBrokerBridge, + IdSchedulerOfThreadPool, + Web.WebReq, + Web.WebBroker, +{$else} + SysUtils, StrUtils, + Types, + IdHTTPWebBrokerBridge, + IdSchedulerOfThreadPool, + WebReq, + WebBroker, +{$endif} + IdContext, + ServerConst in 'ServerConst.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}, + Server.Ignition in 'Server.Ignition.pas'; + +{$R *.res} + +type + TDummyIndyServer = class + public + procedure ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); virtual; + end; + + +procedure StartServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if not (AServer.Active) then + begin + AServer.DefaultPort := TServerEngine.Default.Port; + Writeln(Format(sStartingServer, [AServer.DefaultPort])); + AServer.Active := True; + end + else + Writeln(sServerRunning); + Write(cArrow); +end; + +procedure StopServer(const AServer: TIdHTTPWebBrokerBridge); +begin + if AServer.Active then + begin + Writeln(sStoppingServer); + AServer.Active := False; + Writeln(sServerStopped); + end + else + Writeln(sServerNotRunning); + Write(cArrow); +end; + +procedure SetPort(const AServer: TIdHTTPWebBrokerBridge; const APort: string); +var + LPort: Integer; + LWasActive: Boolean; +begin + LPort := StrToIntDef(APort, -1); + if LPort = -1 then + begin + Writeln('Port should be an integer number. Try again.'); + Exit; + end; + + LWasActive := AServer.Active; + if LWasActive then + StopServer(AServer); + TServerEngine.Default.Port := LPort; + if LWasActive then + StartServer(AServer); + Writeln(Format(sPortSet, [IntToStr(TServerEngine.Default.Port)])); + Write(cArrow); +end; + +procedure WriteCommands; +begin + Writeln(sCommands); + Write(cArrow); +end; + +procedure WriteStatus(const AServer: TIdHTTPWebBrokerBridge); +begin + Writeln(sIndyVersion + AServer.SessionList.Version); + Writeln(sActive + BoolToStr(AServer.Active, True)); + Writeln(sPort + IntToStr(TServerEngine.Default.Port)); + Write(cArrow); +end; + +procedure SetupThreadScheduler(const AServer: TIdHTTPWebBrokerBridge); +var + LScheduler: TIdSchedulerOfThreadPool; +begin + LScheduler := TIdSchedulerOfThreadPool.Create(AServer); + try + LScheduler.PoolSize := TServerEngine.Default.ThreadPoolSize; + AServer.Scheduler := LScheduler; + AServer.MaxConnections := LScheduler.PoolSize; + except + AServer.Scheduler.DisposeOf; + AServer.Scheduler := nil; + raise; + end; +end; + +procedure RunServer(); +var + LServer: TIdHTTPWebBrokerBridge; + LDummyIndy: TDummyIndyServer; + LResponse: string; +begin + WriteCommands; + LDummyIndy := TDummyIndyServer.Create; + try + LServer := TIdHTTPWebBrokerBridge.Create(nil); + try + LServer.DefaultPort := TServerEngine.Default.Port; + LServer.OnParseAuthentication := LDummyIndy.ParseAuthenticationHandler; + {$IFNDEF LINUX} + SetupThreadScheduler(LServer); + {$ENDIF} + + while True do + begin + Readln(LResponse); + LResponse := LowerCase(LResponse); + if sametext(LResponse, cCommandStart) then + StartServer(LServer) + else if sametext(LResponse, cCommandStatus) then + WriteStatus(LServer) + else if sametext(LResponse, cCommandStop) then + StopServer(LServer) + {$ifdef DelphiXE3_UP} + else if LResponse.StartsWith(cCommandSetPort, True) then + SetPort(LServer, LResponse.Split([' '])[2]) + {$else} + else if AnsiStartsText(cCommandSetPort, LResponse) then + SetPort(LServer, Copy(LResponse, Length(cCommandSetPort)+1, MAXINT)) + {$endif} + + else if sametext(LResponse, cCommandHelp) then + WriteCommands + else if sametext(LResponse, cCommandExit) then + if LServer.Active then + begin + StopServer(LServer); + break + end + else + break + else + begin + Writeln(sInvalidCommand); + Write(cArrow); + end; + end; + finally + LServer.Free; + end; + finally + LDummyIndy.Free; + end; +end; + +{ TDummyIndyServer } + +procedure TDummyIndyServer.ParseAuthenticationHandler(AContext: TIdContext; + const AAuthType, AAuthData: String; var VUsername, VPassword: String; + var VHandled: Boolean); +begin + // Allow JWT Bearer authentication's scheme + if SameText(AAuthType, 'Bearer') then + VHandled := True; +end; + +begin + try + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + RunServer(); + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dproj b/Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dproj new file mode 100644 index 00000000..375ad036 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerConsoleApplication.dproj @@ -0,0 +1,1049 @@ + + + {8EEC8DF2-3740-468A-937A-60168245FB71} + UniDACBasicServerConsoleApplication.dpr + True + Release + 4229 + Console + None + 18.7 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + UniDACBasicServerConsoleApplication + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + ./bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + UniDACBasicServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + /usr/X11/bin/xterm -e "%debuggee%" + (None) + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + true + Base + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + + + 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=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + UniDACBasicServer_Icon.ico + (None) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + UniDACBasicServerConsoleApplication_Icon.ico + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + UniDACBasicServer_Icon.ico + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + + + DEBUG;$(DCC_Define) + false + true + + + true + + + Debug + + + true + + + UniDACBasicServer_Icon.ico + + + true + + + true + true + Cfg_2 + true + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + UniDACBasicServer_Icon.ico + + + + MainSource + + + +
ServerWebModule
+ TWebModule +
+ + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + UniDACBasicServerConsoleApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + False + True + True + True + True + False + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + UniDACBasicServerConsoleApplication + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + Assets\ + Logo44x44.png + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\values-v21 + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServerDaemon.dpr b/Demos/UniDAC Basic/UniDACBasicServerDaemon.dpr new file mode 100644 index 00000000..0b5ff770 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerDaemon.dpr @@ -0,0 +1,21 @@ +program UniDACBasicServerDaemon; + +{$APPTYPE CONSOLE} + +{$R *.res} + +uses + Classes, + SysUtils, + {$IFDEF LINUX} + MARS.Linux.Daemon in '..\..\Source\MARS.Linux.Daemon.pas', + {$ENDIF} + Server.Ignition in 'Server.Ignition.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +begin + {$IFDEF LINUX} + TMARSDaemon.Current.Name := 'UniDACBasicServerDaemon'; + TMARSDaemon.Current.Start; + {$ENDIF} +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerDaemon.dproj b/Demos/UniDAC Basic/UniDACBasicServerDaemon.dproj new file mode 100644 index 00000000..ed89f818 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerDaemon.dproj @@ -0,0 +1,1008 @@ + + + {9D225C2C-24C2-48B4-8ABE-52C04AF576AE} + 18.7 + None + UniDACBasicServerDaemon.dpr + True + Debug + Linux64 + 128 + Console + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Base + true + + + .\lib\$(Platform)\$(Config) + .\bin + false + false + false + false + false + RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage) + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + UniDACBasicServerDaemon + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;$(DCC_UsePackage) + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;DataSnapNativeClient;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage) + + + DataSnapServerMidas;FireDACADSDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;inetdb;emsedge;dbexpress;IndyCore;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;bindengine;FireDACOracleDriver;FireDACMySQLDriver;FireDACCommonODBC;DataSnapClient;IndySystem;FireDACDb2Driver;FireDACInfxDriver;emshosting;FireDACPgDriver;FireDACASADriver;FireDACTDataDriver;DbxCommonDriver;DataSnapServer;xmlrtl;DataSnapNativeClient;rtl;DbxClientDriver;CustomIPTransport;bindcomp;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;dbrtl;FireDACMongoDBDriver;IndyProtocols;$(DCC_UsePackage) + /usr/bin/xterm -e "%debuggee%" + UniDACBasicServer_Icon.ico + (None) + + + DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + true + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts + Debug + true + Base + true + DBXSqliteDriver;DataSnapServerMidas;DBXInterBaseDriver;tethering;FireDACMSSQLDriver;bindcompfmx;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;FireDACDBXDriver;dbexpress;IndyCore;dsnap;DataSnapCommon;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;bindcomp;DBXInformixDriver;IndyIPClient;dbxcds;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage);$(DCC_UsePackage) + true + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;CodeSiteExpressPkg;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;RadiantShapesFmx_Design;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(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 + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;RadiantShapesFmx;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;MARS.Utils;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage) + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + /usr/bin/xterm -e "%debuggee%" + + + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Application + + + + UniDACBasicServerDaemon.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + UniDACBasicServerDaemon + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\values-v21 + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + False + False + False + False + True + False + False + False + False + + + 12 + + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.deployproj b/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.deployproj new file mode 100644 index 00000000..25ae74ba --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.deployproj @@ -0,0 +1,132 @@ + + + + 12 + + + + + + iPhone5 + + + + + + + UniDACBasicServerFMXApplication\ + UniDACBasicServerFMXApplication.exe + ProjectOutput + 0 + + + True + True + + + + + UniDACBasicServerFMXApplication.app\Assets\ + Logo44x44.png + UWP_DelphiLogo44 + 0 + + + True + + + UniDACBasicServerFMXApplication.app\Contents\MacOS\ + libcgunwind.1.0.dylib + DependencyModule + 1 + + + True + + + UniDACBasicServerFMXApplication.app\Contents\MacOS\ + UniDACBasicServerFMXApplication.rsm + DebugSymbols + 1 + + + True + + + UniDACBasicServerFMXApplication.app\..\ + UniDACBasicServerFMXApplication.entitlements + ProjectOSXEntitlements + 1 + + + True + + + UniDACBasicServerFMXApplication.app\Contents\Resources\ + UniDACBasicServerFMXApplication.icns + ProjectOSXResource + 1 + + + True + + + UniDACBasicServerFMXApplication.app\Contents\ + Info.plist + ProjectOSXInfoPList + 1 + + + True + + + UniDACBasicServerFMXApplication.app\Contents\MacOS\ + libcgsqlite3.dylib + DependencyModule + 1 + + + True + + + UniDACBasicServerFMXApplication.app\Assets\ + Logo150x150.png + UWP_DelphiLogo150 + 0 + + + True + + + UniDACBasicServerFMXApplication.app\Contents\MacOS\ + UniDACBasicServerFMXApplication + ProjectOutput + 1 + + + True + True + + + + + + UniDACBasicServerFMXApplication.app\ + libPCRE.dylib + DependencyModule + 1 + + + True + + + UniDACBasicServerFMXApplication.app\ + libcgunwind.1.0.dylib + DependencyModule + 1 + + + True + + + diff --git a/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dpr b/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dpr new file mode 100644 index 00000000..ad31a42c --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dpr @@ -0,0 +1,21 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program UniDACBasicServerFMXApplication; + +uses + System.StartUpCopy, + FMX.Forms, + Server.FMX.Forms.Main in 'Server.FMX.Forms.Main.pas' {MainForm}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dproj b/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dproj new file mode 100644 index 00000000..b050412b --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerFMXApplication.dproj @@ -0,0 +1,1212 @@ + + + {2DC28130-9EB1-48C8-9CA6-0F3CFD5D8E1D} + UniDACBasicServerFMXApplication.dpr + True + Release + 5269 + Application + FMX + 18.7 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + 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 + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + UniDACBasicServerFMXApplication + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + .\bin + .\lib\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + 1 + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png + + + /usr/bin/xterm -e "%debuggee%" + (None) + UniDACBasicServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + UniDACBasicServer_Icon.ico + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + Debug + true + true + Base + true + /usr/X11/bin/xterm -e "%debuggee%" + (None) + UniDACBasicServer_Icon.ico + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + UniDACBasicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + UniDACBasicServerFMXApplication_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + Debug + + + true + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + + + true + Cfg_1 + true + true + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + UniDACBasicServer_Icon.ico + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + true + + + Debug + + + true + + + $(BDS)\bin\delphi_PROJECTICNS.icns + true + + + true + true + Cfg_2 + true + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + UniDACBasicServer_Icon.ico + PerMonitor + + + + MainSource + + +
MainForm
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + UniDACBasicServerFMXApplication.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + False + True + False + True + True + True + True + False + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + UniDACBasicServerFMXApplication.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\values-v21 + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServerISAPI.dpr b/Demos/UniDAC Basic/UniDACBasicServerISAPI.dpr new file mode 100644 index 00000000..8a8120dd --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerISAPI.dpr @@ -0,0 +1,30 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +library UniDACBasicServerISAPI; + +uses + Winapi.ActiveX, + System.Win.ComObj, + Web.WebBroker, + Web.Win.ISAPIApp, + Web.Win.ISAPIThreadPool, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +{$R *.res} + +exports + GetExtensionVersion, + HttpExtensionProc, + TerminateExtension; + +begin + CoInitFlags := COINIT_MULTITHREADED; + Application.Initialize; + Application.WebModuleClass := WebModuleClass; + Application.Run; +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerISAPI.dproj b/Demos/UniDAC Basic/UniDACBasicServerISAPI.dproj new file mode 100644 index 00000000..8fd198bc --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerISAPI.dproj @@ -0,0 +1,152 @@ + + + {9715EDC7-B308-42F7-B5EF-14BB4698B974} + UniDACBasicServerISAPI.dpr + True + Release + 1 + Library + None + 18.7 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + true + UniDACBasicServerISAPI + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + .\bin + .\lib\$(Platform)\$(Config) + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + (None) + + + UniDACBasicServerISAPI_Icon.ico + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + (None) + + + + MainSource + + + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + UniDACBasicServerISAPI.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + True + False + + + 12 + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServerService.dpr b/Demos/UniDAC Basic/UniDACBasicServerService.dpr new file mode 100644 index 00000000..456a4e02 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerService.dpr @@ -0,0 +1,43 @@ +(* + Copyright 2016, MARS-Curiosity - REST Library + + Home: https://github.com/andrea-magni/MARS +*) +program UniDACBasicServerService; + +{$I MARS.inc} + +uses +{$ifdef DelphiXE3_UP} + Vcl.SvcMgr, +{$else} + SvcMgr, +{$endif} + + Server.Service in 'Server.Service.pas' {ServerService: TService}, + Server.Ignition in 'Server.Ignition.pas', + Server.Resources in 'Server.Resources.pas', + Server.WebModule in 'Server.WebModule.pas' {ServerWebModule: TWebModule}; + +{$R *.RES} + +begin + // Windows 2003 Server requires StartServiceCtrlDispatcher to be + // called before CoRegisterClassObject, which can be called indirectly + // by Application.Initialize. TServiceApplication.DelayInitialize allows + // Application.Initialize to be called from TService.Main (after + // StartServiceCtrlDispatcher has been called). + // + // Delayed initialization of the Application object may affect + // events which then occur prior to initialization, such as + // TService.OnCreate. It is only recommended if the ServiceApplication + // registers a class object with OLE and is intended for use with + // Windows 2003 Server. + // + // Application.DelayInitialize := True; + // + if not Application.DelayInitialize or Application.Installing then + Application.Initialize; + Application.CreateForm(TServerService, ServerService); + Application.Run; +end. diff --git a/Demos/UniDAC Basic/UniDACBasicServerService.dproj b/Demos/UniDAC Basic/UniDACBasicServerService.dproj new file mode 100644 index 00000000..9b969521 --- /dev/null +++ b/Demos/UniDAC Basic/UniDACBasicServerService.dproj @@ -0,0 +1,1006 @@ + + + {FEF5DF94-50B0-46F7-9F4B-6C355F140A2F} + UniDACBasicServerService.dpr + True + Release + 3 + Application + None + 18.7 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + UniDACBasicServerService + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;Vcl;Winapi;$(DCC_Namespace) + .\lib\$(Platform)\$(Config) + .\bin + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + $(BDS)\bin\default_app.manifest + UniDACBasicServer_Icon.ico + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + $(BDS)\bin\default_app.manifest + UniDACBasicServer_Icon.ico + true + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + true + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + MARS.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + PerMonitor + + + + MainSource + + +
ServerService
+ TService +
+ + + +
ServerWebModule
+ TWebModule +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + UniDACBasicServerService.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + True + True + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + Assets\ + Logo44x44.png + true + + + + + Assets\ + Logo150x150.png + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + Assets\ + Logo44x44.png + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\values-v21 + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + 12 + + + + +
diff --git a/Demos/UniDAC Basic/UniDACBasicServer_Icon.ico b/Demos/UniDAC Basic/UniDACBasicServer_Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0067d86ba25beefed52ee5181c1b8865b4e6c8ba GIT binary patch literal 67646 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 - - {8C6A1564-94E6-420D-BDE4-54A4CD3BD125} - MARS.Core.dpk - True - Release - 131 - Package - None - 18.5 - Win32 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - true - Cfg_2 - true - true - - - false - false - false - false - false - 00400000 - true - true - MARS_Core - MARS-Curiosity Core - 260 - true - true - 1040 - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= - System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - ..\..\Source;$(DCC_UnitSearchPath) - ..\..\Lib260\$(Platform)\$(Config) - - - package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= - Debug - android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar - - - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - Debug - true - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) - 1033 - - - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - true - 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 - - - RELEASE;$(DCC_Define) - 0 - false - 0 - - - true - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) - - - DEBUG;$(DCC_Define) - false - true - - - Debug - - - true - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - - - - MainSource - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - Package - - - - MARS.Core.dpk - - - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 - - - - False - False - False - False - True - False - True - True - - - 12 - - - -
+ + + {8C6A1564-94E6-420D-BDE4-54A4CD3BD125} + MARS.Core.dpk + True + Release + 131 + Package + None + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + true + true + MARS_Core + MARS-Curiosity Core + 260 + true + true + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + ..\..\Source;$(DCC_UnitSearchPath) + ..\..\Lib260\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.Core.dpk + + + DBExpress Enterprise Data Explorer Integration + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + CodeSite Express 5.3.3 + + + + False + False + False + False + False + True + False + True + True + + + 12 + + + + diff --git a/Packages/103Rio/MARS.Core.res b/Packages/103Rio/MARS.Core.res index 8d2bc4840b2a920860127e5fbb50886551cf1df9..3e070e286612e71d1f891a7bc1b7ac7449a1354c 100644 GIT binary patch delta 12 TcmZo*ZD8F{!^FbOz`y_i6o3Li delta 12 TcmZo*ZD8F{!^G0ez`y_i7Jvf4 diff --git a/Packages/103Rio/MARS.DCS.dpk b/Packages/103Rio/MARS.DCS.dpk new file mode 100644 index 00000000..f0c7eac1 --- /dev/null +++ b/Packages/103Rio/MARS.DCS.dpk @@ -0,0 +1,57 @@ +package MARS.DCS; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library - DelphiCrossSocket support'} +{$LIBSUFFIX '260'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + MARS.Utils, + MARS.Core; + +contains + MARS.http.Server.DCS in '..\..\Source\MARS.http.Server.DCS.pas', + Net.CrossHttpMiddleware in '..\..\ThirdParty\DCS\Net\Net.CrossHttpMiddleware.pas', + Net.CrossHttpParams in '..\..\ThirdParty\DCS\Net\Net.CrossHttpParams.pas', + Net.CrossHttpRouter in '..\..\ThirdParty\DCS\Net\Net.CrossHttpRouter.pas', + Net.CrossHttpServer in '..\..\ThirdParty\DCS\Net\Net.CrossHttpServer.pas', + Net.CrossHttpUtils in '..\..\ThirdParty\DCS\Net\Net.CrossHttpUtils.pas', + Net.CrossServer in '..\..\ThirdParty\DCS\Net\Net.CrossServer.pas', + Net.CrossSocket.Iocp in '..\..\ThirdParty\DCS\Net\Net.CrossSocket.Iocp.pas', + Net.CrossSocket in '..\..\ThirdParty\DCS\Net\Net.CrossSocket.pas', + Net.SocketAPI in '..\..\ThirdParty\DCS\Net\Net.SocketAPI.pas', + Net.Winsock2 in '..\..\ThirdParty\DCS\Net\Net.Winsock2.pas', + Net.Wship6 in '..\..\ThirdParty\DCS\Net\Net.Wship6.pas', + Net.CrossSocket.Base in '..\..\ThirdParty\DCS\Net\Net.CrossSocket.Base.pas', + Utils.DateTime in '..\..\ThirdParty\DCS\Utils\Utils.DateTime.pas', + Utils.Logger in '..\..\ThirdParty\DCS\Utils\Utils.Logger.pas', + Utils.RegEx in '..\..\ThirdParty\DCS\Utils\Utils.RegEx.pas', + Utils.Utils in '..\..\ThirdParty\DCS\Utils\Utils.Utils.pas'; + +end. diff --git a/Packages/103Rio/MARS.DCS.dproj b/Packages/103Rio/MARS.DCS.dproj new file mode 100644 index 00000000..0531394e --- /dev/null +++ b/Packages/103Rio/MARS.DCS.dproj @@ -0,0 +1,1068 @@ + + + {02BBCED0-86EE-4FB8-A124-5BD6E5477506} + MARS.DCS.dpk + 18.8 + None + True + Release + Win32 + 3 + Package + + + true + + + true + Base + true + + + true + Base + 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 + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + ..\..\Lib260\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + true + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + All + MARS_DCS + true + true + 1040 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + MARS-Curiosity REST Library - DelphiCrossSocket support + 260 + ..\..\Source;$(DCC_UnitSearchPath) + + + None + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + None + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + None + + + None + + + None + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + 1033 + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 1033 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + true + 1033 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + + + true + 1033 + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.DCS.dpk + + + DBExpress Enterprise Data Explorer Integration + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + CodeSite Express 5.3.3 + + + + + + true + + + + + MARS_DCS.bpl + true + + + + + MARS_DCS.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARS_DCS.bpl + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + False + False + False + False + False + False + False + True + True + + + 12 + + + + + diff --git a/Packages/103Rio/MARS.DCS.res b/Packages/103Rio/MARS.DCS.res new file mode 100644 index 0000000000000000000000000000000000000000..6dbcdc6ada87e9f059eb00361de77e30f79b86f0 GIT binary patch literal 640 zcmZvaK~KU!6ojYPgZ1Rqn+MONnqD*{##l8ZDh3;_icn1xXideR=fCmpKd^rLN-YpJ zdCRhIW_EVAl5|K_Rb4FCN2lrUgYm2vD)9e7orvW~k9uOh(vVeO1Ksn#&wS0=jW7S_ zS91JXqxn6zuH*!p&T>o7YB2mB73-3-+z7S1!X3daVYi%Pb-^?xZg~Ly0aSsrM3uW5F3tH8ss`oX9+{b2goC`gRUIUd_2`l4_Hc yj_g$uyHQR8Gmxv<1^&0s=b;`|ReL0GOPYzj`H-6>mhBBMk}Q*T*|y)#4!l3_15y$I literal 0 HcmV?d00001 diff --git a/Packages/103Rio/MARS.DelphiRazor.dpk b/Packages/103Rio/MARS.DelphiRazor.dpk index 58085036..19c36215 100644 --- a/Packages/103Rio/MARS.DelphiRazor.dpk +++ b/Packages/103Rio/MARS.DelphiRazor.dpk @@ -26,7 +26,7 @@ package MARS.DelphiRazor; {$DEFINE DEBUG} {$ENDIF IMPLICITBUILDING} {$DESCRIPTION 'MARS-Curiosity Core'} -{$LIBSUFFIX '250'} +{$LIBSUFFIX '260'} {$RUNONLY} {$IMPLICITBUILD OFF} diff --git a/Packages/103Rio/MARS.DelphiRazor.dproj b/Packages/103Rio/MARS.DelphiRazor.dproj index d8e4b85f..15fb892c 100644 --- a/Packages/103Rio/MARS.DelphiRazor.dproj +++ b/Packages/103Rio/MARS.DelphiRazor.dproj @@ -58,7 +58,7 @@ true ..\..\Source;$(DCC_UnitSearchPath) true - 250 + 260 MARS-Curiosity REST Library CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 1040 @@ -168,7 +168,7 @@ true - + MARS_DelphiRazor.bpl true diff --git a/Packages/103Rio/MARS.FireDAC.dpk b/Packages/103Rio/MARS.FireDAC.dpk index 1b1dc2d6..b86f459f 100644 --- a/Packages/103Rio/MARS.FireDAC.dpk +++ b/Packages/103Rio/MARS.FireDAC.dpk @@ -9,23 +9,23 @@ package MARS.FireDAC; {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} -{$LOCALSYMBOLS OFF} +{$LOCALSYMBOLS ON} {$LONGSTRINGS ON} {$OPENSTRINGS ON} -{$OPTIMIZATION ON} +{$OPTIMIZATION OFF} {$OVERFLOWCHECKS OFF} {$RANGECHECKS OFF} -{$REFERENCEINFO OFF} +{$REFERENCEINFO ON} {$SAFEDIVIDE OFF} -{$STACKFRAMES OFF} +{$STACKFRAMES ON} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000} -{$DEFINE RELEASE} +{$DEFINE DEBUG} {$ENDIF IMPLICITBUILDING} -{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$DESCRIPTION 'MARS-Curiosity FireDAC'} {$LIBSUFFIX '260'} {$RUNONLY} {$IMPLICITBUILD OFF} diff --git a/Packages/103Rio/MARS.FireDAC.dproj b/Packages/103Rio/MARS.FireDAC.dproj index f7063962..ce2bee02 100644 --- a/Packages/103Rio/MARS.FireDAC.dproj +++ b/Packages/103Rio/MARS.FireDAC.dproj @@ -2,7 +2,7 @@ {D44362A0-1BCF-45FD-80B4-281FB36C0E08} MARS.FireDAC.dpk - 18.5 + 18.8 None True Release @@ -34,12 +34,6 @@ Base true - - true - Cfg_2 - true - true - true true @@ -88,11 +82,6 @@ 0 0 - - true - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - MainSource @@ -140,10 +129,8 @@ MARS.FireDAC.dpk - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components @@ -152,6 +139,12 @@ true + + + bplMARS_FireDAC.so + true + + true @@ -177,18 +170,12 @@ true - + MARS_FireDAC.bpl true - - - bplMARS_FireDAC.so - true - - 1 @@ -202,12 +189,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -220,12 +215,26 @@ library\lib\armeabi 1 + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + library\lib\mips 1 + + library\lib\mips + 1 + @@ -233,84 +242,216 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + res\drawable 1 + + res\drawable + 1 + res\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -380,6 +521,9 @@ 0 + + 0 + 0 @@ -410,6 +554,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -421,6 +576,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -432,6 +620,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -443,6 +686,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -476,10 +829,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -523,6 +901,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -545,6 +927,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -582,6 +970,7 @@ + True diff --git a/Packages/103Rio/MARS.FireDAC.res b/Packages/103Rio/MARS.FireDAC.res index 83681a4bb52ed7d8da3aa4c7f0f39c8bc053e8f9..527eb12d83ee60c7082bcb44a765c2f19442fa94 100644 GIT binary patch delta 68 zcmbQi+Q2eFL8*a}fkA>KwbF^DGa4R{Ur>342cZ6Koda@W5A(#ayers0QN>DV*mgE diff --git a/Packages/103Rio/MARS.JOSE.dpk b/Packages/103Rio/MARS.JOSE.dpk index 84cf46b4..8cbee885 100644 --- a/Packages/103Rio/MARS.JOSE.dpk +++ b/Packages/103Rio/MARS.JOSE.dpk @@ -39,20 +39,30 @@ requires MARS.Core; contains + MARS.JOSEJWT.Token in '..\..\Source\MARS.JOSEJWT.Token.pas', + MARS.JOSEJWT.Token.InjectionService in '..\..\Source\MARS.JOSEJWT.Token.InjectionService.pas', + JOSE.Builder in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Builder.pas', + JOSE.Consumer in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Consumer.pas', + JOSE.Consumer.Validators in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Consumer.Validators.pas', + JOSE.Context in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Context.pas', JOSE.Core.Base in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.Base.pas', JOSE.Core.Builder in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.Builder.pas', + JOSE.Core.JWA.Compression in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Compression.pas', + JOSE.Core.JWA.Encryption in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Encryption.pas', + JOSE.Core.JWA.Factory in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Factory.pas', JOSE.Core.JWA in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.pas', + JOSE.Core.JWA.Signing in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Signing.pas', JOSE.Core.JWE in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWE.pas', JOSE.Core.JWK in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWK.pas', JOSE.Core.JWS in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWS.pas', JOSE.Core.JWT in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWT.pas', JOSE.Core.Parts in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.Parts.pas', - JOSE.Cryptography.RSA in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Cryptography.RSA.pas', JOSE.Encoding.Base64 in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Encoding.Base64.pas', JOSE.Hashing.HMAC in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Hashing.HMAC.pas', + JOSE.Signing.RSA in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Signing.RSA.pas', + JOSE.Types.Arrays in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Types.Arrays.pas', JOSE.Types.Bytes in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Types.Bytes.pas', - MARS.JOSEJWT.Token in '..\..\Source\MARS.JOSEJWT.Token.pas', - MARS.JOSEJWT.Token.InjectionService in '..\..\Source\MARS.JOSEJWT.Token.InjectionService.pas'; + JOSE.Types.JSON in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Types.JSON.pas'; end. diff --git a/Packages/103Rio/MARS.JOSE.dproj b/Packages/103Rio/MARS.JOSE.dproj index b7f4e8f2..4076d19b 100644 --- a/Packages/103Rio/MARS.JOSE.dproj +++ b/Packages/103Rio/MARS.JOSE.dproj @@ -2,7 +2,7 @@ {16057797-C497-46DA-B133-A45E7F564D8D} MARS.JOSE.dpk - 18.5 + 18.8 None True Release @@ -136,20 +136,30 @@ + + + + + + + + + + - + + - - + Cfg_2 Base @@ -171,10 +181,8 @@ MARS.JOSE.dpk - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components @@ -183,12 +191,6 @@ true - - - MARS_JOSE.bpl - true - - true @@ -214,6 +216,18 @@ true + + + MARS_JOSE.bpl + true + + + + + MARS_JOSE.bpl + true + + 1 @@ -227,12 +241,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -245,12 +267,26 @@ library\lib\armeabi 1 + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + library\lib\mips 1 + + library\lib\mips + 1 + @@ -258,84 +294,216 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + res\drawable 1 + + res\drawable + 1 + res\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -405,6 +573,9 @@ 0 + + 0 + 0 @@ -435,6 +606,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -446,6 +628,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -457,6 +672,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -468,6 +738,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -501,10 +881,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -548,6 +953,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -570,6 +979,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -607,6 +1022,7 @@ + True diff --git a/Packages/103Rio/MARS.ReadersAndWriters.dpk b/Packages/103Rio/MARS.ReadersAndWriters.dpk index 015a5758..00ee25cf 100644 --- a/Packages/103Rio/MARS.ReadersAndWriters.dpk +++ b/Packages/103Rio/MARS.ReadersAndWriters.dpk @@ -9,23 +9,23 @@ package MARS.ReadersAndWriters; {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} -{$LOCALSYMBOLS OFF} +{$LOCALSYMBOLS ON} {$LONGSTRINGS ON} {$OPENSTRINGS ON} -{$OPTIMIZATION ON} +{$OPTIMIZATION OFF} {$OVERFLOWCHECKS OFF} {$RANGECHECKS OFF} -{$REFERENCEINFO OFF} +{$REFERENCEINFO ON} {$SAFEDIVIDE OFF} -{$STACKFRAMES OFF} +{$STACKFRAMES ON} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000} -{$DEFINE RELEASE} +{$DEFINE DEBUG} {$ENDIF IMPLICITBUILDING} -{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$DESCRIPTION 'MARS-Curiosity Readers and Writers'} {$LIBSUFFIX '260'} {$RUNONLY} {$IMPLICITBUILD OFF} diff --git a/Packages/103Rio/MARS.ReadersAndWriters.dproj b/Packages/103Rio/MARS.ReadersAndWriters.dproj index 7997e9a5..17160347 100644 --- a/Packages/103Rio/MARS.ReadersAndWriters.dproj +++ b/Packages/103Rio/MARS.ReadersAndWriters.dproj @@ -1,603 +1,992 @@ - - - {5170D912-4303-47AD-85A6-D77D3D0874DA} - MARS.ReadersAndWriters.dpk - 18.5 - None - True - Release - Win32 - 131 - Package - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - true - ..\..\Source;$(DCC_UnitSearchPath) - true - 260 - MARS-Curiosity REST Library - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - 1040 - true - System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - true - MARS_ReadersAndWriters - ..\..\Lib260\$(Platform)\$(Config) - .\$(Platform)\$(Config) - false - false - false - false - false - - - true - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - - - 1033 - true - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - - - DEBUG;$(DCC_Define) - true - false - true - true - true - - - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - MARS-Curiosity Readers and Writers - true - 1033 - false - - - true - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - - - false - RELEASE;$(DCC_Define) - 0 - 0 - - - true - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - - - - MainSource - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - Package - - - - MARS.ReadersAndWriters.dpk - - - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 - - - - - - true - - - - - MARS_ReadersAndWriters.bpl - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - - - library\lib\mips - 1 - - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - - - res\values - 1 - - - - - res\values-v21 - 1 - - - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - True - True - True - - - 12 - - - - - + + + {5170D912-4303-47AD-85A6-D77D3D0874DA} + MARS.ReadersAndWriters.dpk + 18.8 + None + True + Release + Win32 + 131 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + ..\..\Source;$(DCC_UnitSearchPath) + true + 260 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_ReadersAndWriters + ..\..\Lib260\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity Readers and Writers + true + 1033 + false + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.ReadersAndWriters.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARS_ReadersAndWriters.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + True + + + 12 + + + + + diff --git a/Packages/103Rio/MARS.ReadersAndWriters.res b/Packages/103Rio/MARS.ReadersAndWriters.res index f389daf52a76301a3fc1ce33ee2fb1db68e24141..7039c993bdd3eedfa68a03730e384caed4cb98d6 100644 GIT binary patch delta 12 Tcmcb@dWChv1tu0|1_lNI9s~nC delta 12 Tcmcb@dWChv1tyke1_lNIAOr)v diff --git a/Packages/103Rio/MARS.UniDAC.dpk b/Packages/103Rio/MARS.UniDAC.dpk new file mode 100644 index 00000000..304921fa --- /dev/null +++ b/Packages/103Rio/MARS.UniDAC.dpk @@ -0,0 +1,47 @@ +package MARS.UniDAC; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$LIBSUFFIX '260'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + dbrtl, + MARS.Core, + MARS.ReadersAndWriters, + unidac260, + dac260; + +contains + MARS.Data.UniDAC.InjectionService in '..\..\Source\MARS.Data.UniDAC.InjectionService.pas', + MARS.Data.UniDAC in '..\..\Source\MARS.Data.UniDAC.pas', + MARS.Data.UniDAC.ReadersAndWriters in '..\..\Source\MARS.Data.UniDAC.ReadersAndWriters.pas', + MARS.Data.UniDAC.Utils in '..\..\Source\MARS.Data.UniDAC.Utils.pas'; + +end. diff --git a/Packages/103Rio/MARS.UniDAC.dproj b/Packages/103Rio/MARS.UniDAC.dproj new file mode 100644 index 00000000..db7e6063 --- /dev/null +++ b/Packages/103Rio/MARS.UniDAC.dproj @@ -0,0 +1,989 @@ + + + {0B4F7444-23A8-436B-80C9-F0387EFF9DCA} + MARS.UniDAC.dpk + 18.8 + FMX + True + Release + Win32 + 1 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + true + ..\..\Source;$(DCC_UnitSearchPath) + 260 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_UniDAC + ..\..\Lib260\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + rtl;dbrtl;fmx;inet;IndySystem;IndyProtocols;IndyCore;FireDAC;FireDACCommonDriver;FireDACCommon;fmxFireDAC;dsnap;DataSnapFireDAC;$(DCC_UsePackage) + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity FireDAC + true + 1033 + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + + MainSource + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.UniDAC.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARS_UniDAC.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARS_UniDAC.bpl + true + + + + + bplMARS_UniDAC.so + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + + + 12 + + + + + diff --git a/Packages/103Rio/MARS.UniDAC.res b/Packages/103Rio/MARS.UniDAC.res new file mode 100644 index 0000000000000000000000000000000000000000..9b419e5462941239305d79c726cfa60ff1649f3d GIT binary patch literal 652 zcmZ{i%}T>S6opSp7Pe&J1GsSEx}=bWf}q9vQ_xmw>{eopCD1mK=;Qe|?tKH}HxtJ; zg$~@w&D?WN&Yc@cIwY#94${U7TCTni#$?$6p_dK&z$q2g2-;ov^c<8;WP^OjUsAXCxMR}!HxnA_48DHIIZF6EB{B!0o6D@Hzu=+D}mguMK&r}K& z6O@gQobx;N?Ok>Ch@%WvUl%aX6>Rt(fG=U1{@-x4_P>jNZ28STM%GzseeL2I*B%`k zr|#C)w=5mm3B^@~ literal 0 HcmV?d00001 diff --git a/Packages/103Rio/MARS.Utils.dpk b/Packages/103Rio/MARS.Utils.dpk index 87907dc2..5d05fd26 100644 --- a/Packages/103Rio/MARS.Utils.dpk +++ b/Packages/103Rio/MARS.Utils.dpk @@ -9,23 +9,23 @@ package MARS.Utils; {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} -{$LOCALSYMBOLS OFF} +{$LOCALSYMBOLS ON} {$LONGSTRINGS ON} {$OPENSTRINGS ON} -{$OPTIMIZATION ON} +{$OPTIMIZATION OFF} {$OVERFLOWCHECKS OFF} {$RANGECHECKS OFF} -{$REFERENCEINFO OFF} +{$REFERENCEINFO ON} {$SAFEDIVIDE OFF} -{$STACKFRAMES OFF} +{$STACKFRAMES ON} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000} -{$DEFINE RELEASE} +{$DEFINE DEBUG} {$ENDIF IMPLICITBUILDING} -{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$DESCRIPTION 'MARS-Curiosity Utils'} {$LIBSUFFIX '260'} {$RUNONLY} {$IMPLICITBUILD OFF} @@ -51,6 +51,9 @@ contains MARS.Core.Exceptions in '..\..\Source\MARS.Core.Exceptions.pas', MARS.Core.Declarations in '..\..\Source\MARS.Core.Declarations.pas', MARS.Core.MediaType in '..\..\Source\MARS.Core.MediaType.pas', - MARS.Utils.JWT in '..\..\Source\MARS.Utils.JWT.pas'; + MARS.Utils.JWT in '..\..\Source\MARS.Utils.JWT.pas', + MARS.Core.RequestAndResponse.Interfaces in '..\..\Source\MARS.Core.RequestAndResponse.Interfaces.pas'; end. + + diff --git a/Packages/103Rio/MARS.Utils.dproj b/Packages/103Rio/MARS.Utils.dproj index 11357f6b..29e50c05 100644 --- a/Packages/103Rio/MARS.Utils.dproj +++ b/Packages/103Rio/MARS.Utils.dproj @@ -1,616 +1,1006 @@ - - - {A5387B60-F9BE-4AEF-8E11-EFB0EDCAB2ED} - MARS.Utils.dpk - 18.5 - None - True - Release - Win32 - 131 - Package - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - true - ..\..\Source;$(DCC_UnitSearchPath) - true - 260 - MARS-Curiosity REST Library - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - 1040 - true - System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - true - MARS_Utils - ..\..\Lib260\$(Platform)\$(Config) - .\$(Platform)\$(Config) - false - false - false - false - false - - - true - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - - - 1033 - true - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - - - DEBUG;$(DCC_Define) - true - false - true - true - true - - - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - MARS-Curiosity Utils - true - 1033 - false - - - true - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - - - false - RELEASE;$(DCC_Define) - 0 - 0 - - - true - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - - - - MainSource - - - - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - Package - - - - MARS.Utils.dpk - - - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 - - - - - - true - - - - - MARS_Utils.bpl - true - - - - - MARS_Utils.bpl - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - - - library\lib\mips - 1 - - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - - - res\values - 1 - - - - - res\values-v21 - 1 - - - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - True - True - True - - - 12 - - - - - + + + {A5387B60-F9BE-4AEF-8E11-EFB0EDCAB2ED} + MARS.Utils.dpk + 18.8 + None + True + Release + Win32 + 131 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + ..\..\Source;$(DCC_UnitSearchPath) + true + 260 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_Utils + ..\..\Lib260\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity Utils + true + 1033 + false + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.Utils.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARS_Utils.bpl + true + + + + + MARS_Utils.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + True + + + 12 + + + + + diff --git a/Packages/103Rio/MARS.Utils.res b/Packages/103Rio/MARS.Utils.res index a098ca418cfd908c0bc2f09c241403116a887065..973053c2bdd0c027c44ab27ca6dff8bf5b27b32a 100644 GIT binary patch delta 12 TcmeBS?P1-}!NkJMz`y_i72E=3 delta 12 TcmeBS?P1-}!Nk(cz`y_i7u*8m diff --git a/Packages/103Rio/MARS.groupproj b/Packages/103Rio/MARS.groupproj index 50591fbf..9bf614c5 100644 --- a/Packages/103Rio/MARS.groupproj +++ b/Packages/103Rio/MARS.groupproj @@ -9,6 +9,9 @@ + + + @@ -47,6 +50,15 @@ + + + + + + + + + @@ -84,13 +96,13 @@ - + - + - + diff --git a/Packages/103Rio/MARS.mORMot.dpk b/Packages/103Rio/MARS.mORMot.dpk index 67db9ee8..47901b85 100644 --- a/Packages/103Rio/MARS.mORMot.dpk +++ b/Packages/103Rio/MARS.mORMot.dpk @@ -44,6 +44,7 @@ contains SynCrypto in '..\..\ThirdParty\mORMot\Source\SynCrypto.pas', SynEcc in '..\..\ThirdParty\mORMot\Source\SynEcc.pas', SynLZ in '..\..\ThirdParty\mORMot\Source\SynLZ.pas', - MARS.mORMotJWT.Token.InjectionService in '..\..\Source\MARS.mORMotJWT.Token.InjectionService.pas'; + MARS.mORMotJWT.Token.InjectionService in '..\..\Source\MARS.mORMotJWT.Token.InjectionService.pas', + SynTable in '..\..\ThirdParty\mORMot\Source\SynTable.pas'; end. diff --git a/Packages/103Rio/MARS.mORMot.dproj b/Packages/103Rio/MARS.mORMot.dproj index c89188a0..974ab08e 100644 --- a/Packages/103Rio/MARS.mORMot.dproj +++ b/Packages/103Rio/MARS.mORMot.dproj @@ -2,7 +2,7 @@ {CFDDCD9D-D0A9-42FE-8036-193E21B00BD9} MARS.mORMot.dpk - 18.5 + 18.8 None True Release @@ -143,6 +143,7 @@ + Cfg_2 Base @@ -164,10 +165,8 @@ MARS.mORMot.dpk - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components @@ -182,6 +181,12 @@ true + + + MARS_mORMot.bpl + true + + true @@ -220,12 +225,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -238,12 +251,26 @@ library\lib\armeabi 1 + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + library\lib\mips 1 + + library\lib\mips + 1 + @@ -251,84 +278,216 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + res\drawable 1 + + res\drawable + 1 + res\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -398,6 +557,9 @@ 0 + + 0 + 0 @@ -428,6 +590,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -439,6 +612,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -450,6 +656,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -461,6 +722,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -494,10 +865,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -541,6 +937,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -563,6 +963,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -600,6 +1006,7 @@ + True diff --git a/Packages/103Rio/MARS.mORMot.res b/Packages/103Rio/MARS.mORMot.res index 6f0c9b7e1739d390e260faaa488df3ca704abff0..75115b7086dcfcdf437acfa7af8ff1b73fc2578f 100644 GIT binary patch delta 12 TcmeBS?P1-}!NkJMz`y_i72E=3 delta 12 TcmeBS?P1-}!Nk(cz`y_i7u*8m diff --git a/Packages/103Rio/MARSClient.Core.dpk b/Packages/103Rio/MARSClient.Core.dpk index 6652152c..10f9e95f 100644 --- a/Packages/103Rio/MARSClient.Core.dpk +++ b/Packages/103Rio/MARSClient.Core.dpk @@ -51,6 +51,7 @@ contains MARS.Client.Utils in '..\..\Source\MARS.Client.Utils.pas', MARS.Client.Client in '..\..\Source\MARS.Client.Client.pas', MARS.Client.Client.Indy in '..\..\Source\MARS.Client.Client.Indy.pas', - MARS.Client.Resource.FormData in '..\..\Source\MARS.Client.Resource.FormData.pas'; + MARS.Client.Resource.FormData in '..\..\Source\MARS.Client.Resource.FormData.pas', + MARS.Client.Resource.FormUrlEncoded in '..\..\Source\MARS.Client.Resource.FormUrlEncoded.pas'; end. diff --git a/Packages/103Rio/MARSClient.Core.dproj b/Packages/103Rio/MARSClient.Core.dproj index 0f45b144..8f652bfc 100644 --- a/Packages/103Rio/MARSClient.Core.dproj +++ b/Packages/103Rio/MARSClient.Core.dproj @@ -2,7 +2,7 @@ {48E75129-EE24-48D4-AE54-2F549C909CC8} MARSClient.Core.dpk - 18.5 + 18.8 FMX True Release @@ -87,7 +87,7 @@ true - 250 + 260 MARS-Curiosity Client Core 1033 true @@ -126,6 +126,7 @@ + BITMAP TMARSCLIENTMESSAGINGRESOURCE @@ -195,10 +196,8 @@ MARSClient.Core.dpk - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components @@ -207,15 +206,8 @@ true - - - MARSClient_Core.bpl - true - - - - - MARSClient_Core.bpl + + true @@ -229,6 +221,12 @@ true + + + MARSClient_Core.bpl + true + + true @@ -239,13 +237,14 @@ true - - + + + MARSClient_Core.bpl true - - + + true @@ -262,12 +261,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -280,12 +287,26 @@ library\lib\armeabi 1 + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + library\lib\mips 1 + + library\lib\mips + 1 + @@ -293,84 +314,216 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + res\drawable 1 + + res\drawable + 1 + res\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -440,6 +593,9 @@ 0 + + 0 + 0 @@ -470,6 +626,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -481,6 +648,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -492,6 +692,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -503,6 +758,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -536,10 +901,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -583,6 +973,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -605,6 +999,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -642,6 +1042,7 @@ + True diff --git a/Packages/103Rio/MARSClient.CoreDesign.dproj b/Packages/103Rio/MARSClient.CoreDesign.dproj index 783e1067..ce455dd6 100644 --- a/Packages/103Rio/MARSClient.CoreDesign.dproj +++ b/Packages/103Rio/MARSClient.CoreDesign.dproj @@ -2,7 +2,7 @@ {8EEA5DAB-B80F-44EA-B149-45D051C20610} MARSClient.CoreDesign.dpk - 18.5 + 18.8 FMX True Release @@ -34,12 +34,6 @@ true true - - true - Cfg_1 - true - true - true Base @@ -51,12 +45,6 @@ true true - - true - Cfg_2 - true - true - 260 ../../Source/Core;../../Source/Data/FireDAC;$(DCC_UnitSearchPath) @@ -82,12 +70,14 @@ rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARS.Utils;MARSClient.Core;$(DCC_UsePackage) 1033 true + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARS.Utils;$(DCC_UsePackage) 1033 true + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) @@ -99,15 +89,12 @@ true + 260 MARS-Curiosity Client Core 1033 true false - - true - 1033 - false RELEASE;$(DCC_Define) @@ -117,10 +104,7 @@ true 1033 - - - true - 1033 + 260 @@ -165,6 +149,12 @@ true + + + MARSClient_CoreDesign.bpl + true + + true @@ -190,12 +180,6 @@ true - - - MARSClient_CoreDesign.bpl - true - - true @@ -214,12 +198,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -232,12 +224,26 @@ library\lib\armeabi 1 + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + library\lib\mips 1 + + library\lib\mips + 1 + @@ -245,84 +251,216 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + res\drawable 1 + + res\drawable + 1 + res\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -392,6 +530,9 @@ 0 + + 0 + 0 @@ -422,6 +563,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -433,6 +585,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -444,6 +629,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -455,6 +695,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -488,10 +838,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -535,6 +910,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -557,6 +936,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -594,6 +979,7 @@ + True diff --git a/Packages/103Rio/MARSClient.CoreDesign.res b/Packages/103Rio/MARSClient.CoreDesign.res index a19c323923f3bcdf1ba9809cfc04577435af0edf..da606b6bb1303f6e6788e6f7c5b137eab537d9a6 100644 GIT binary patch delta 70 zcmX@X+Q2eFL8*a}fkAwJs)p9y1GyN!3(NU1|&%(jX*Y z#)=&!xrmKiQ?=E6t~rb-U}(vQzn!mKz*<*g(-Ui<}1 H4(9p>#b7eF diff --git a/Packages/103Rio/MARSClient.CoreResource.rc b/Packages/103Rio/MARSClient.CoreResource.rc new file mode 100644 index 00000000..d2788dbf --- /dev/null +++ b/Packages/103Rio/MARSClient.CoreResource.rc @@ -0,0 +1,12 @@ +TMARSCLIENTMESSAGINGRESOURCE BITMAP "..\\..\\media\\TMARSClientMessagingResource.bmp" +TMARSCLIENTRESOURCE BITMAP "..\\..\\media\\TMARSClientResource.bmp" +TMARSCLIENTRESOURCEJSON BITMAP "..\\..\\media\\TMARSClientResourceJSON.bmp" +TMARSCLIENTRESOURCESTREAM BITMAP "..\\..\\media\\TMARSClientResourceStream.bmp" +TMARSCLIENTSUBRESOURCE BITMAP "..\\..\\media\\TMARSClientSubResource.bmp" +TMARSCLIENTSUBRESOURCEJSON BITMAP "..\\..\\media\\TMARSClientSubResourceJSON.bmp" +TMARSCLIENTSUBRESOURCESTREAM BITMAP "..\\..\\media\\TMARSClientSubResourceStream.bmp" +TMARSCLIENTTOKEN BITMAP "..\\..\\media\\TMARSClientToken.bmp" +TMARSCLIENT BITMAP "..\\..\\media\\TMARSClient.bmp" +TMARSCLIENTAPPLICATION BITMAP "..\\..\\media\\TMARSClientApplication.bmp" +TMARSNETCLIENT BITMAP "..\\..\\media\\TMARSNetClient.bmp" +TMARSCLIENTRESOURCEFORMDATA BITMAP "..\\..\\media\\TMARSClientResourceFormData.bmp" diff --git a/Packages/103Rio/MARSClient.FireDAC.dproj b/Packages/103Rio/MARSClient.FireDAC.dproj index 1fa3f2e1..2e954ac0 100644 --- a/Packages/103Rio/MARSClient.FireDAC.dproj +++ b/Packages/103Rio/MARSClient.FireDAC.dproj @@ -2,7 +2,7 @@ {29A0C2A3-BA6B-44DF-B909-A20D8DBF1B9B} MARSClient.FireDAC.dpk - 18.5 + 18.8 FMX True Release @@ -123,10 +123,8 @@ MARSClient.FireDAC.dpk - DBExpress Enterprise Data Explorer Integration - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - CodeSite Express 5.3.3 + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components @@ -160,14 +158,14 @@ true - - - MARSClient_FireDAC.bpl + + true - - + + + MARSClient_FireDAC.bpl true @@ -184,12 +182,20 @@ classes 1 + + classes + 1 + res\xml 1 + + res\xml + 1 + @@ -202,12 +208,26 @@ library\lib\armeabi 1 + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + library\lib\mips 1 + + library\lib\mips + 1 + @@ -215,84 +235,216 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + res\drawable 1 + + res\drawable + 1 + res\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -362,6 +514,9 @@ 0 + + 0 + 0 @@ -392,6 +547,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -403,6 +569,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -414,6 +613,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -425,6 +679,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -458,10 +822,35 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 + + 1 + @@ -505,6 +894,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -527,6 +920,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -564,6 +963,7 @@ + True diff --git a/Packages/103Rio/MARSClient.FireDACDesign.dproj b/Packages/103Rio/MARSClient.FireDACDesign.dproj index 6ab349c9..3a0bc7ba 100644 --- a/Packages/103Rio/MARSClient.FireDACDesign.dproj +++ b/Packages/103Rio/MARSClient.FireDACDesign.dproj @@ -2,7 +2,7 @@ {6F52BB40-74E2-410E-A54D-0C20E713A0CE} MARSClient.FireDACDesign.dpk - 18.5 + 18.8 FMX True Release @@ -62,7 +62,7 @@ true - rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARSClient.FireDAC;MARSClient.CoreDesign250;MARSClient.FireDAC250;$(DCC_UsePackage) + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARSClient.FireDAC;MARSClient.CoreDesign260;MARSClient.FireDAC260;$(DCC_UsePackage) 1033 true CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) @@ -77,7 +77,7 @@ true - 250 + 260 MARS-Curiosity Client FireDAC 1033 true @@ -159,7 +159,7 @@ true - + MARSClient_FireDACDesign.bpl true @@ -202,6 +202,12 @@ 1 + + + library\lib\armeabi-v7a + 1 + + library\lib\mips @@ -215,6 +221,12 @@ 1 + + + library\lib\armeabi-v7a + 1 + + res\drawable @@ -233,6 +245,16 @@ 1 + + + res\values + 1 + + + res\values + 1 + + res\drawable @@ -269,6 +291,56 @@ 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 @@ -293,6 +365,16 @@ 1 + + + res\values + 1 + + + res\values + 1 + + 1 @@ -391,6 +473,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -402,6 +495,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -413,6 +539,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -424,6 +605,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -457,6 +748,28 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -526,6 +839,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -563,6 +882,7 @@ + True diff --git a/Packages/104Sydney/MARS.Core.dpk b/Packages/104Sydney/MARS.Core.dpk new file mode 100644 index 00000000..089e5f6e --- /dev/null +++ b/Packages/104Sydney/MARS.Core.dpk @@ -0,0 +1,68 @@ +package MARS.Core; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity Core'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + dbrtl, + inet, + IndySystem, + IndyProtocols, + IndyCore, + xmlrtl, + dsnap, + MARS.Utils; + +contains + MARS.Core.Application in '..\..\Source\MARS.Core.Application.pas', + MARS.Core.Attributes in '..\..\Source\MARS.Core.Attributes.pas', + MARS.Core.Cache in '..\..\Source\MARS.Core.Cache.pas', + MARS.Core.Classes in '..\..\Source\MARS.Core.Classes.pas', + MARS.Core.Engine in '..\..\Source\MARS.Core.Engine.pas', + MARS.Core.MessageBodyReader in '..\..\Source\MARS.Core.MessageBodyReader.pas', + MARS.Core.MessageBodyWriter in '..\..\Source\MARS.Core.MessageBodyWriter.pas', + MARS.Core.Registry in '..\..\Source\MARS.Core.Registry.pas', + MARS.Core.Response in '..\..\Source\MARS.Core.Response.pas', + MARS.Core.Token in '..\..\Source\MARS.Core.Token.pas', + MARS.http.Core in '..\..\Source\MARS.http.Core.pas', + MARS.Core.Activation in '..\..\Source\MARS.Core.Activation.pas', + MARS.Core.Injection.Interfaces in '..\..\Source\MARS.Core.Injection.Interfaces.pas', + MARS.Core.Injection in '..\..\Source\MARS.Core.Injection.pas', + MARS.Core.Injection.Types in '..\..\Source\MARS.Core.Injection.Types.pas', + MARS.Core.Activation.InjectionService in '..\..\Source\MARS.Core.Activation.InjectionService.pas', + MARS.Core.Activation.Interfaces in '..\..\Source\MARS.Core.Activation.Interfaces.pas', + MARS.Core.Registry.Utils in '..\..\Source\MARS.Core.Registry.Utils.pas'; + +end. + + + + diff --git a/Packages/104Sydney/MARS.Core.dproj b/Packages/104Sydney/MARS.Core.dproj new file mode 100644 index 00000000..3224e9c2 --- /dev/null +++ b/Packages/104Sydney/MARS.Core.dproj @@ -0,0 +1,770 @@ + + + {8C6A1564-94E6-420D-BDE4-54A4CD3BD125} + MARS.Core.dpk + True + Release + 131 + Package + None + 19.0 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + true + true + MARS_Core + MARS-Curiosity Core + 270 + true + true + 1040 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + ..\..\Source;$(DCC_UnitSearchPath) + ..\..\Lib270\$(Platform)\$(Config) + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 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 + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + + + DEBUG;$(DCC_Define) + false + true + + + Debug + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.Core.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + False + True + True + True + + + + + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ..\$(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\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + + diff --git a/Packages/104Sydney/MARS.DCS.dpk b/Packages/104Sydney/MARS.DCS.dpk new file mode 100644 index 00000000..df091358 --- /dev/null +++ b/Packages/104Sydney/MARS.DCS.dpk @@ -0,0 +1,57 @@ +package MARS.DCS; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library - DelphiCrossSocket support'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + MARS.Utils, + MARS.Core; + +contains + MARS.http.Server.DCS in '..\..\Source\MARS.http.Server.DCS.pas', + Net.CrossHttpMiddleware in '..\..\ThirdParty\DCS\Net\Net.CrossHttpMiddleware.pas', + Net.CrossHttpParams in '..\..\ThirdParty\DCS\Net\Net.CrossHttpParams.pas', + Net.CrossHttpRouter in '..\..\ThirdParty\DCS\Net\Net.CrossHttpRouter.pas', + Net.CrossHttpServer in '..\..\ThirdParty\DCS\Net\Net.CrossHttpServer.pas', + Net.CrossHttpUtils in '..\..\ThirdParty\DCS\Net\Net.CrossHttpUtils.pas', + Net.CrossServer in '..\..\ThirdParty\DCS\Net\Net.CrossServer.pas', + Net.CrossSocket.Iocp in '..\..\ThirdParty\DCS\Net\Net.CrossSocket.Iocp.pas', + Net.CrossSocket in '..\..\ThirdParty\DCS\Net\Net.CrossSocket.pas', + Net.SocketAPI in '..\..\ThirdParty\DCS\Net\Net.SocketAPI.pas', + Net.Winsock2 in '..\..\ThirdParty\DCS\Net\Net.Winsock2.pas', + Net.Wship6 in '..\..\ThirdParty\DCS\Net\Net.Wship6.pas', + Net.CrossSocket.Base in '..\..\ThirdParty\DCS\Net\Net.CrossSocket.Base.pas', + Utils.DateTime in '..\..\ThirdParty\DCS\Utils\Utils.DateTime.pas', + Utils.Logger in '..\..\ThirdParty\DCS\Utils\Utils.Logger.pas', + Utils.RegEx in '..\..\ThirdParty\DCS\Utils\Utils.RegEx.pas', + Utils.Utils in '..\..\ThirdParty\DCS\Utils\Utils.Utils.pas'; + +end. diff --git a/Packages/104Sydney/MARS.DCS.dproj b/Packages/104Sydney/MARS.DCS.dproj new file mode 100644 index 00000000..62a4d12c --- /dev/null +++ b/Packages/104Sydney/MARS.DCS.dproj @@ -0,0 +1,1117 @@ + + + {02BBCED0-86EE-4FB8-A124-5BD6E5477506} + MARS.DCS.dpk + 19.0 + None + True + Release + Win32 + 3 + Package + + + true + + + true + Base + 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 + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + true + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + All + MARS_DCS + true + true + 1040 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + MARS-Curiosity REST Library - DelphiCrossSocket support + 270 + ..\..\Source;$(DCC_UnitSearchPath) + + + None + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + true + Base + true + None + android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar + + + None + + + None + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + 1033 + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 1033 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + true + 1033 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + + + true + 1033 + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.DCS.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARS_DCS.bpl + true + + + + + true + + + + + true + + + + + MARS_DCS.bpl + true + + + + + true + + + + + true + + + + + MARS_DCS.bpl + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + False + False + False + False + False + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARS.FireDAC.dpk b/Packages/104Sydney/MARS.FireDAC.dpk new file mode 100644 index 00000000..c22644a3 --- /dev/null +++ b/Packages/104Sydney/MARS.FireDAC.dpk @@ -0,0 +1,59 @@ +package MARS.FireDAC; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + dbrtl, + FireDAC, + FireDACCommonDriver, + FireDACCommon, + inet, + IndySystem, + IndyProtocols, + IndyCore, + dsnap, + MARS.Core, + MARS.ReadersAndWriters; + +contains + MARS.Data.FireDAC.DataModule in '..\..\Source\MARS.Data.FireDAC.DataModule.pas' {MARSFDDataModuleResource: TDataModule}, + MARS.Data.FireDAC.ReadersAndWriters in '..\..\Source\MARS.Data.FireDAC.ReadersAndWriters.pas', + MARS.Data.FireDAC in '..\..\Source\MARS.Data.FireDAC.pas', + MARS.Data.FireDAC.InjectionService in '..\..\Source\MARS.Data.FireDAC.InjectionService.pas', + MARS.Data.FireDAC.Resources in '..\..\Source\MARS.Data.FireDAC.Resources.pas', + MARS.Data.FireDAC.Utils in '..\..\Source\MARS.Data.FireDAC.Utils.pas'; + +end. + + + + diff --git a/Packages/104Sydney/MARS.FireDAC.dproj b/Packages/104Sydney/MARS.FireDAC.dproj new file mode 100644 index 00000000..55439df2 --- /dev/null +++ b/Packages/104Sydney/MARS.FireDAC.dproj @@ -0,0 +1,1062 @@ + + + {D44362A0-1BCF-45FD-80B4-281FB36C0E08} + MARS.FireDAC.dpk + 19.0 + None + True + Release + Win32 + 1 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + true + ..\..\Source;$(DCC_UnitSearchPath) + 270 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_FireDAC + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + rtl;dbrtl;fmx;inet;IndySystem;IndyProtocols;IndyCore;FireDAC;FireDACCommonDriver;FireDACCommon;fmxFireDAC;dsnap;DataSnapFireDAC;$(DCC_UsePackage) + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity FireDAC + true + 1033 + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + + MainSource + + + + + + + + + + + + + + +
MARSFDDataModuleResource
+ dfm + TDataModule +
+ + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Package + + + + MARS.FireDAC.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + bplMARS_FireDAC.so + true + + + + + true + + + + + MARS_FireDAC.bpl + true + + + + + MARS_FireDAC.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + + + 12 + + + + +
diff --git a/Packages/104Sydney/MARS.JOSE.dpk b/Packages/104Sydney/MARS.JOSE.dpk new file mode 100644 index 00000000..e1a81f4b --- /dev/null +++ b/Packages/104Sydney/MARS.JOSE.dpk @@ -0,0 +1,70 @@ +package MARS.JOSE; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + IndySystem, + IndyProtocols, + IndyCore, + MARS.Utils, + MARS.Core; + +contains + MARS.JOSEJWT.Token in '..\..\Source\MARS.JOSEJWT.Token.pas', + MARS.JOSEJWT.Token.InjectionService in '..\..\Source\MARS.JOSEJWT.Token.InjectionService.pas', + JOSE.Builder in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Builder.pas', + JOSE.Consumer in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Consumer.pas', + JOSE.Consumer.Validators in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Consumer.Validators.pas', + JOSE.Context in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Context.pas', + JOSE.Core.Base in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.Base.pas', + JOSE.Core.Builder in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.Builder.pas', + JOSE.Core.JWA.Compression in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Compression.pas', + JOSE.Core.JWA.Encryption in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Encryption.pas', + JOSE.Core.JWA.Factory in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Factory.pas', + JOSE.Core.JWA in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.pas', + JOSE.Core.JWA.Signing in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWA.Signing.pas', + JOSE.Core.JWE in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWE.pas', + JOSE.Core.JWK in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWK.pas', + JOSE.Core.JWS in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWS.pas', + JOSE.Core.JWT in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.JWT.pas', + JOSE.Core.Parts in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Core.Parts.pas', + JOSE.Encoding.Base64 in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Encoding.Base64.pas', + JOSE.Hashing.HMAC in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Hashing.HMAC.pas', + JOSE.Signing.RSA in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Signing.RSA.pas', + JOSE.Types.Arrays in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Types.Arrays.pas', + JOSE.Types.Bytes in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Types.Bytes.pas', + JOSE.Types.JSON in '..\..\ThirdParty\delphi-jose-jwt\Source\JOSE.Types.JSON.pas'; + +end. + + + diff --git a/Packages/104Sydney/MARS.JOSE.dproj b/Packages/104Sydney/MARS.JOSE.dproj new file mode 100644 index 00000000..561b3025 --- /dev/null +++ b/Packages/104Sydney/MARS.JOSE.dproj @@ -0,0 +1,1098 @@ + + + {16057797-C497-46DA-B133-A45E7F564D8D} + MARS.JOSE.dpk + 19.0 + None + True + Release + Win32 + 3 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + ..\..\Source;$(DCC_UnitSearchPath) + true + 270 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_JOSE + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity JOSE + true + 1033 + false + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.JOSE.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARS_JOSE.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARS_JOSE.bpl + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARS.ReadersAndWriters.dpk b/Packages/104Sydney/MARS.ReadersAndWriters.dpk new file mode 100644 index 00000000..5047895f --- /dev/null +++ b/Packages/104Sydney/MARS.ReadersAndWriters.dpk @@ -0,0 +1,49 @@ +package MARS.ReadersAndWriters; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + dbrtl, + inet, + IndySystem, + IndyProtocols, + IndyCore, + dsnap, + MARS.Utils, + MARS.Core; + +contains + MARS.Core.MessageBodyWriters in '..\..\Source\MARS.Core.MessageBodyWriters.pas', + MARS.Data.MessageBodyWriters in '..\..\Source\MARS.Data.MessageBodyWriters.pas', + MARS.Core.MessageBodyReaders in '..\..\Source\MARS.Core.MessageBodyReaders.pas'; + +end. diff --git a/Packages/104Sydney/MARS.ReadersAndWriters.dproj b/Packages/104Sydney/MARS.ReadersAndWriters.dproj new file mode 100644 index 00000000..b0f45927 --- /dev/null +++ b/Packages/104Sydney/MARS.ReadersAndWriters.dproj @@ -0,0 +1,1070 @@ + + + {5170D912-4303-47AD-85A6-D77D3D0874DA} + MARS.ReadersAndWriters.dpk + 19.0 + None + True + Release + Win32 + 131 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + ..\..\Source;$(DCC_UnitSearchPath) + true + 270 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_ReadersAndWriters + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity Readers and Writers + true + 1033 + false + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + + MainSource + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.ReadersAndWriters.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARS_ReadersAndWriters.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARS_ReadersAndWriters.bpl + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARS.Utils.dpk b/Packages/104Sydney/MARS.Utils.dpk new file mode 100644 index 00000000..214c65ad --- /dev/null +++ b/Packages/104Sydney/MARS.Utils.dpk @@ -0,0 +1,59 @@ +package MARS.Utils; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + dbrtl, + inet, + IndySystem, + IndyProtocols, + IndyCore, + RESTComponents; + +contains + MARS.Core.Utils in '..\..\Source\MARS.Core.Utils.pas', + MARS.Data.Utils in '..\..\Source\MARS.Data.Utils.pas', + MARS.Rtti.Utils in '..\..\Source\MARS.Rtti.Utils.pas', + MARS.Utils.Parameters.IniFile in '..\..\Source\MARS.Utils.Parameters.IniFile.pas', + MARS.Utils.Parameters in '..\..\Source\MARS.Utils.Parameters.pas', + MARS.Utils.Parameters.JSON in '..\..\Source\MARS.Utils.Parameters.JSON.pas', + MARS.Core.JSON in '..\..\Source\MARS.Core.JSON.pas', + MARS.Core.URL in '..\..\Source\MARS.Core.URL.pas', + MARS.Core.Exceptions in '..\..\Source\MARS.Core.Exceptions.pas', + MARS.Core.Declarations in '..\..\Source\MARS.Core.Declarations.pas', + MARS.Core.MediaType in '..\..\Source\MARS.Core.MediaType.pas', + MARS.Utils.JWT in '..\..\Source\MARS.Utils.JWT.pas', + MARS.Core.RequestAndResponse.Interfaces in '..\..\Source\MARS.Core.RequestAndResponse.Interfaces.pas'; + +end. + + diff --git a/Packages/104Sydney/MARS.Utils.dproj b/Packages/104Sydney/MARS.Utils.dproj new file mode 100644 index 00000000..e0729929 --- /dev/null +++ b/Packages/104Sydney/MARS.Utils.dproj @@ -0,0 +1,1078 @@ + + + {A5387B60-F9BE-4AEF-8E11-EFB0EDCAB2ED} + MARS.Utils.dpk + 19.0 + None + True + Release + Win32 + 131 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + ..\..\Source;$(DCC_UnitSearchPath) + true + 270 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_Utils + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity Utils + true + 1033 + false + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.Utils.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + true + + + + + true + + + + + MARS_Utils.bpl + true + + + + + true + + + + + MARS_Utils.bpl + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARS.groupproj b/Packages/104Sydney/MARS.groupproj new file mode 100644 index 00000000..9bf614c5 --- /dev/null +++ b/Packages/104Sydney/MARS.groupproj @@ -0,0 +1,108 @@ + + + {79526BDC-090B-468F-A29A-B97CAFE42EA6} + + + + + + + + + + + + + + + + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Packages/104Sydney/MARS.mORMot.dpk b/Packages/104Sydney/MARS.mORMot.dpk new file mode 100644 index 00000000..949786c7 --- /dev/null +++ b/Packages/104Sydney/MARS.mORMot.dpk @@ -0,0 +1,50 @@ +package MARS.mORMot; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + IndySystem, + IndyProtocols, + IndyCore, + MARS.Utils, + MARS.Core; + +contains + MARS.mORMotJWT.Token in '..\..\Source\MARS.mORMotJWT.Token.pas', + SynCommons in '..\..\ThirdParty\mORMot\Source\SynCommons.pas', + SynCrypto in '..\..\ThirdParty\mORMot\Source\SynCrypto.pas', + SynEcc in '..\..\ThirdParty\mORMot\Source\SynEcc.pas', + SynLZ in '..\..\ThirdParty\mORMot\Source\SynLZ.pas', + MARS.mORMotJWT.Token.InjectionService in '..\..\Source\MARS.mORMotJWT.Token.InjectionService.pas', + SynTable in '..\..\ThirdParty\mORMot\Source\SynTable.pas'; + +end. diff --git a/Packages/104Sydney/MARS.mORMot.dproj b/Packages/104Sydney/MARS.mORMot.dproj new file mode 100644 index 00000000..50a5f0d1 --- /dev/null +++ b/Packages/104Sydney/MARS.mORMot.dproj @@ -0,0 +1,1083 @@ + + + {CFDDCD9D-D0A9-42FE-8036-193E21B00BD9} + MARS.mORMot.dpk + 19.0 + None + True + Release + Win32 + 131 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + true + ..\..\Source;$(DCC_UnitSearchPath) + true + 270 + MARS-Curiosity REST Library + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + 1040 + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + MARS_mORMot + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + false + + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + MARS-Curiosity mORMot JWT + true + 1033 + false + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + true + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + + MainSource + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARS.mORMot.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + true + + + + + MARS_mORMot.bpl + true + + + + + true + + + + + true + + + + + MARS_mORMot.bpl + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARSClient.Core.dpk b/Packages/104Sydney/MARSClient.Core.dpk new file mode 100644 index 00000000..13c439e1 --- /dev/null +++ b/Packages/104Sydney/MARSClient.Core.dpk @@ -0,0 +1,57 @@ +package MARSClient.Core; + +{$R *.res} +{$R *.dres} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library (Client)'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + dbrtl, + inet, + IndySystem, + IndyProtocols, + IndyCore, + MARS.Utils; + +contains + MARS.Client.Application in '..\..\Source\MARS.Client.Application.pas', + MARS.Client.Client.Net in '..\..\Source\MARS.Client.Client.Net.pas', + MARS.Client.CustomResource in '..\..\Source\MARS.Client.CustomResource.pas', + MARS.Client.Resource.JSON in '..\..\Source\MARS.Client.Resource.JSON.pas', + MARS.Client.Resource in '..\..\Source\MARS.Client.Resource.pas', + MARS.Client.Resource.Stream in '..\..\Source\MARS.Client.Resource.Stream.pas', + MARS.Client.Token in '..\..\Source\MARS.Client.Token.pas', + MARS.Client.Utils in '..\..\Source\MARS.Client.Utils.pas', + MARS.Client.Client in '..\..\Source\MARS.Client.Client.pas', + MARS.Client.Client.Indy in '..\..\Source\MARS.Client.Client.Indy.pas', + MARS.Client.Resource.FormData in '..\..\Source\MARS.Client.Resource.FormData.pas', + MARS.Client.Resource.FormUrlEncoded in '..\..\Source\MARS.Client.Resource.FormUrlEncoded.pas'; + +end. diff --git a/Packages/104Sydney/MARSClient.Core.dproj b/Packages/104Sydney/MARSClient.Core.dproj new file mode 100644 index 00000000..59576bdb --- /dev/null +++ b/Packages/104Sydney/MARSClient.Core.dproj @@ -0,0 +1,1118 @@ + + + {48E75129-EE24-48D4-AE54-2F549C909CC8} + MARSClient.Core.dpk + 19.0 + FMX + True + Release + Win32 + 3 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + 270 + ../../Source/Core;../../Source/Data/FireDAC;$(DCC_UnitSearchPath) + MARS-Curiosity REST Library (Client) + -LUDesignIDE + System;Xml;Data;Datasnap;Web;Soap;Winapi;Vcl;$(DCC_Namespace) + true + MARSClient_Core + true + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + 2057 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + true + true + + + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARS.Utils;$(DCC_UsePackage) + 1033 + true + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + + + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARS.Utils;$(DCC_UsePackage) + 1033 + true + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + 260 + MARS-Curiosity Client Core + 1033 + true + false + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + BITMAP + TMARSCLIENTMESSAGINGRESOURCE + + + BITMAP + TMARSCLIENTRESOURCE + + + BITMAP + TMARSCLIENTRESOURCEJSON + + + BITMAP + TMARSCLIENTRESOURCESTREAM + + + BITMAP + TMARSCLIENTSUBRESOURCE + + + BITMAP + TMARSCLIENTSUBRESOURCEJSON + + + BITMAP + TMARSCLIENTSUBRESOURCESTREAM + + + BITMAP + TMARSCLIENTTOKEN + + + BITMAP + TMARSCLIENT + + + BITMAP + TMARSCLIENTAPPLICATION + + + BITMAP + TMARSNETCLIENT + + + BITMAP + TMARSCLIENTRESOURCEFORMDATA + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARSClient.Core.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARSClient_Core.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARSClient_Core.bpl + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARSClient.CoreDesign.dpk b/Packages/104Sydney/MARSClient.CoreDesign.dpk new file mode 100644 index 00000000..80d1e6a9 --- /dev/null +++ b/Packages/104Sydney/MARSClient.CoreDesign.dpk @@ -0,0 +1,46 @@ +package MARSClient.CoreDesign; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library (Client)'} +{$LIBSUFFIX '270'} +{$DESIGNONLY} +{$IMPLICITBUILD OFF} + +requires + bindengine, + bindcomp, + MARSClient.Core; + +contains + MARS.Client.Utils.LiveBindings in '..\..\Source\MARS.Client.Utils.LiveBindings.pas', + MARS.Client.Register in '..\..\Source\MARS.Client.Register.pas', + MARS.Client.CustomResource.Editor in '..\..\Source\MARS.Client.CustomResource.Editor.pas'; + +end. + + + diff --git a/Packages/104Sydney/MARSClient.CoreDesign.dproj b/Packages/104Sydney/MARSClient.CoreDesign.dproj new file mode 100644 index 00000000..548795e2 --- /dev/null +++ b/Packages/104Sydney/MARSClient.CoreDesign.dproj @@ -0,0 +1,1058 @@ + + + {8EEA5DAB-B80F-44EA-B149-45D051C20610} + MARSClient.CoreDesign.dpk + 19.0 + FMX + True + Release + Win32 + 3 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + 270 + ../../Source/Core;../../Source/Data/FireDAC;$(DCC_UnitSearchPath) + MARS-Curiosity REST Library (Client) + -LUDesignIDE + System;Xml;Data;Datasnap;Web;Soap;Winapi;Vcl;$(DCC_Namespace) + true + MARSClient_CoreDesign + true + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + 2057 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + true + true + + + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARS.Utils;MARSClient.Core;$(DCC_UsePackage) + 1033 + true + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + + + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARS.Utils;$(DCC_UsePackage) + 1033 + true + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + 260 + MARS-Curiosity Client Core + 1033 + true + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + + + + MainSource + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARSClient.CoreDesign.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + MARSClient_CoreDesign.bpl + true + + + + + MARSClient_CoreDesign.bpl + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARSClient.FireDAC.dpk b/Packages/104Sydney/MARSClient.FireDAC.dpk new file mode 100644 index 00000000..e4734309 --- /dev/null +++ b/Packages/104Sydney/MARSClient.FireDAC.dpk @@ -0,0 +1,49 @@ +package MARSClient.FireDAC; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library (Client)'} +{$LIBSUFFIX '270'} +{$RUNONLY} +{$IMPLICITBUILD OFF} + +requires + rtl, + MARSClient.Core, + FireDAC, + FireDACCommonDriver, + FireDACCommon; + +contains + MARS.Client.FireDAC in '..\..\Source\MARS.Client.FireDAC.pas', + MARS.Data.FireDAC.Utils in '..\..\Source\MARS.Data.FireDAC.Utils.pas'; + +end. + + + + + diff --git a/Packages/104Sydney/MARSClient.FireDAC.dproj b/Packages/104Sydney/MARSClient.FireDAC.dproj new file mode 100644 index 00000000..06ea1514 --- /dev/null +++ b/Packages/104Sydney/MARSClient.FireDAC.dproj @@ -0,0 +1,1044 @@ + + + {29A0C2A3-BA6B-44DF-B909-A20D8DBF1B9B} + MARSClient.FireDAC.dpk + 19.0 + FMX + True + Release + Win32 + 1 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + 270 + ../../Source/Core;../../Source/Data/FireDAC;$(DCC_UnitSearchPath) + MARS-Curiosity REST Library (Client) + -LUDesignIDE + System;Xml;Data;Datasnap;Web;Soap;Winapi;Vcl;$(DCC_Namespace) + true + MARSClient_FireDAC + true + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + 2057 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + true + true + + + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;$(DCC_UsePackage) + 1033 + true + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + MARS-Curiosity Client FireDAC + 1033 + true + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + + + + MainSource + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARSClient.FireDAC.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARSClient_FireDAC.bpl + true + + + + + MARSClient_FireDAC.bpl + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARSClient.FireDACDesign.dpk b/Packages/104Sydney/MARSClient.FireDACDesign.dpk new file mode 100644 index 00000000..7b0a2a7e --- /dev/null +++ b/Packages/104Sydney/MARSClient.FireDACDesign.dpk @@ -0,0 +1,46 @@ +package MARSClient.FireDACDesign; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS OFF} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE RELEASE} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'MARS-Curiosity REST Library (Client) - FireDAC'} +{$LIBSUFFIX '270'} +{$DESIGNONLY} +{$IMPLICITBUILD OFF} + +requires + MARSClient.FireDAC, + MARSClient.CoreDesign; + +contains + MARS.Data.FireDAC.Editor in '..\..\Source\MARS.Data.FireDAC.Editor.pas', + MARS.Client.FireDAC.Register in '..\..\Source\MARS.Client.FireDAC.Register.pas'; + +end. + + + + + diff --git a/Packages/104Sydney/MARSClient.FireDACDesign.dproj b/Packages/104Sydney/MARSClient.FireDACDesign.dproj new file mode 100644 index 00000000..937f937b --- /dev/null +++ b/Packages/104Sydney/MARSClient.FireDACDesign.dproj @@ -0,0 +1,1043 @@ + + + {6F52BB40-74E2-410E-A54D-0C20E713A0CE} + MARSClient.FireDACDesign.dpk + 19.0 + FMX + True + Release + Win32 + 1 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + 270 + true + ../../Source/Core;../../Source/Data/FireDAC;$(DCC_UnitSearchPath) + MARS-Curiosity REST Library (Client) - FireDAC + -LUDesignIDE + System;Xml;Data;Datasnap;Web;Soap;Winapi;Vcl;$(DCC_Namespace) + true + MARSClient_FireDACDesign + true + ..\..\Lib270\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + 2057 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + true + + + rtl;inet;DbxCommonDriver;dbrtl;FireDACCommon;FireDACCommonDriver;FireDAC;DataSnapFireDAC;IndySystem;IndyProtocols;IndyCore;bindengine;bindcomp;MARSClient.FireDAC;MARSClient.CoreDesign260;MARSClient.FireDAC260;$(DCC_UsePackage) + 1033 + true + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + 260 + MARS-Curiosity Client FireDAC + 1033 + true + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + 1033 + + + + MainSource + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Package + + + + MARSClient.FireDACDesign.dpk + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + true + + + + + MARSClient_FireDACDesign.bpl + true + + + + + true + + + + + MARSClient_FireDACDesign.bpl + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 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\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 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 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + + + 12 + + + + + diff --git a/Packages/104Sydney/MARSClient.groupproj b/Packages/104Sydney/MARSClient.groupproj new file mode 100644 index 00000000..41ffc820 --- /dev/null +++ b/Packages/104Sydney/MARSClient.groupproj @@ -0,0 +1,84 @@ + + + {8B31503E-A2F0-4933-8CB5-13BB39CA0F69} + + + + + + + + + + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/MARS.Client.Client.Net.pas b/Source/MARS.Client.Client.Net.pas index d57eb084..07476414 100644 --- a/Source/MARS.Client.Client.Net.pas +++ b/Source/MARS.Client.Client.Net.pas @@ -11,11 +11,11 @@ interface uses SysUtils, Classes - , MARS.Core.JSON, MARS.Client.Utils, MARS.Core.Utils, MARS.Client.Client + , MARS.Core.JSON, MARS.Client.Utils, MARS.Core.Utils, MARS.Client.Client, MARS.Utils.Parameters // Net , System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent - , System.Net.Mime +, System.Net.Mime ; type @@ -38,7 +38,6 @@ TMARSNetClient = class(TMARSCustomClient) function CreateMultipartFormData(AFormData: TArray): TMultipartFormData; - procedure EndorseAuthorization; override; procedure CheckLastCmdSuccess; virtual; procedure ApplyProxyConfig; override; @@ -58,6 +57,9 @@ TMARSNetClient = class(TMARSCustomClient) procedure Post(const AURL: string; const AFormData: TArray; const AResponse: TStream; const AAuthToken: string; const AAccept: string; const AContentType: string); override; + procedure Post(const AURL: string; const AFormUrlEncoded: TMARSParameters; + const AResponse: TStream; + const AAuthToken: string; const AAccept: string; const AContentType: string); override; procedure Put(const AURL: string; AContent, AResponse: TStream; const AAuthToken: string; const AAccept: string; const AContentType: string); override; @@ -254,10 +256,8 @@ function TMARSNetClient.GetReadTimeout: Integer; function TMARSNetClient.LastCmdSuccess: Boolean; begin - if assigned(FLastResponse) then - Result := (FLastResponse.StatusCode >= 200) and (FLastResponse.StatusCode < 300) - else - Result := FALSE; + Result := Assigned(FLastResponse) + and ((FLastResponse.StatusCode >= 200) and (FLastResponse.StatusCode < 300)); end; procedure TMARSNetClient.Post(const AURL: string; AContent, AResponse: TStream; @@ -284,18 +284,16 @@ procedure TMARSNetClient.Put(const AURL: string; AContent, AResponse: TStream; function TMARSNetClient.ResponseStatusCode: Integer; begin - if assigned(FLastResponse) then - Result := FLastResponse.StatusCode - else - Result := -1; + Result := -1; + if Assigned(FLastResponse) then + Result := FLastResponse.StatusCode; end; function TMARSNetClient.ResponseText: string; begin - if assigned(FLastResponse) then - Result := FLastResponse.StatusText - else - Result := ''; + Result := ''; + if Assigned(FLastResponse) then + Result := FLastResponse.StatusText; end; procedure TMARSNetClient.SetConnectTimeout(const Value: Integer); @@ -345,6 +343,24 @@ procedure TMARSNetClient.Post(const AURL: string; end; end; +procedure TMARSNetClient.Post(const AURL: string; const AFormUrlEncoded: TMARSParameters; const AResponse: TStream; + const AAuthToken, AAccept, AContentType: string); +var + LFormUrlEncoded: TStrings; +begin + inherited; + + FHttpClient.Accept := AAccept; + FHttpClient.ContentType := AContentType; + LFormUrlEncoded := AFormUrlEncoded.AsStrings; + try + FLastResponse := FHttpClient.Post(AURL, LFormUrlEncoded, AResponse); + CheckLastCmdSuccess; + finally + LFormUrlEncoded.Free; + end; +end; + procedure TMARSNetClient.Put(const AURL: string; const AFormData: System.TArray; const AResponse: TStream; const AAuthToken, AAccept: string; const AContentType: string); @@ -368,5 +384,4 @@ procedure TMARSNetClient.Put(const AURL: string; end; end; - -end. +end. \ No newline at end of file diff --git a/Source/MARS.Client.Client.pas b/Source/MARS.Client.Client.pas index 9a9b2266..648fe2a8 100755 --- a/Source/MARS.Client.Client.pas +++ b/Source/MARS.Client.Client.pas @@ -15,6 +15,7 @@ interface , MARS.Core.Utils , MARS.Core.MediaType , MARS.Client.Utils +, MARS.Utils.Parameters ; type @@ -91,11 +92,17 @@ TMARSCustomClient = class(TComponent) procedure Post(const AURL: string; const AFormData: TArray; const AResponse: TStream; const AAuthToken: string; const AAccept: string; const AContentType: string); overload; virtual; + procedure Post(const AURL: string; const AFormUrlEncoded: TMARSParameters; + const AResponse: TStream; + const AAuthToken: string; const AAccept: string; const AContentType: string); overload; virtual; procedure Put(const AURL: string; AContent, AResponse: TStream; const AAuthToken: string; const AAccept: string; const AContentType: string); overload; virtual; procedure Put(const AURL: string; const AFormData: TArray; const AResponse: TStream; const AAuthToken: string; const AAccept: string; const AContentType: string); overload; virtual; + procedure Put(const AURL: string; const AFormUrlEncoded: TMARSParameters; + const AResponse: TStream; + const AAuthToken: string; const AAccept: string; const AContentType: string); overload; virtual; function LastCmdSuccess: Boolean; virtual; function ResponseStatusCode: Integer; virtual; @@ -612,6 +619,13 @@ procedure TMARSCustomClient.Post(const AURL: string; FireBeforeExecute(AURL, Self); end; +procedure TMARSCustomClient.Post(const AURL: string; const AFormUrlEncoded: TMARSParameters; const AResponse: TStream; + const AAuthToken, AAccept, AContentType: string); +begin + FAuthToken := AAuthToken; + BeforeExecute; +end; + class function TMARSCustomClient.PostJSON(const AEngineURL, AAppName, AResourceName: string; const APathParams: TArray; const AQueryParams: TStrings; const AContent: TJSONValue; const ACompletionHandler: TProc; const AToken: string @@ -794,6 +808,12 @@ class function TMARSCustomClient.PostStream(const AEngineURL, AAppName, end; end; +procedure TMARSCustomClient.Put(const AURL: string; const AFormUrlEncoded: TMARSParameters; const AResponse: TStream; + const AAuthToken, AAccept, AContentType: string); +begin + FAuthToken := AAuthToken; + BeforeExecute; +end; procedure TMARSCustomClient.Put(const AURL: string; const AFormData: TArray; const AResponse: TStream; diff --git a/Source/MARS.Client.Register.pas b/Source/MARS.Client.Register.pas index d77c2cd3..64096a1b 100644 --- a/Source/MARS.Client.Register.pas +++ b/Source/MARS.Client.Register.pas @@ -11,7 +11,8 @@ implementation MARS.Client.Client.Indy, MARS.Client.Application, MARS.Client.Resource, MARS.Client.Resource.FormData, MARS.Client.Resource.JSON, MARS.Client.Resource.Stream, MARS.Client.Token, MARS.Client.Client.Net, - MARS.Client.CustomResource, MARS.Client.CustomResource.Editor; + MARS.Client.CustomResource, MARS.Client.CustomResource.Editor, + MARS.Client.Resource.FormUrlEncoded; procedure Register; begin @@ -19,6 +20,7 @@ procedure Register; TMARSClientApplication, TMARSClientResource, TMARSClientResourceFormData, + TMARSClientResourceFormUrlEncoded, TMARSClientResourceJSON, TMARSClientResourceStream, TMARSClientToken, diff --git a/Source/MARS.Client.Resource.FormUrlEncoded.pas b/Source/MARS.Client.Resource.FormUrlEncoded.pas new file mode 100644 index 00000000..b25c9681 --- /dev/null +++ b/Source/MARS.Client.Resource.FormUrlEncoded.pas @@ -0,0 +1,248 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) + +unit MARS.Client.Resource.FormUrlEncoded; + +{$I MARS.inc} + +interface + +uses + SysUtils, Classes, Generics.Collections + + , MARS.Client.Resource, MARS.Client.CustomResource, MARS.Client.Client + , MARS.Client.Utils, MARS.Core.Utils, MARS.Utils.Parameters, MARS.Core.JSON +; + +type + [ComponentPlatformsAttribute(pidAllPlatforms)] + TMARSClientResourceFormUrlEncoded = class(TMARSClientResource) + private + FFormUrlEncoded: TMARSParameters; + FResponse: TMemoryStream; + protected + procedure AssignTo(Dest: TPersistent); override; + procedure AfterPOST(const AContent: TStream); override; + procedure AfterPUT(const AContent: TStream); override; + function GetResponseAsString: string; override; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + + procedure POST( + const ABeforeExecute: TProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AAfterExecute: TMARSClientResponseProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AOnException: TMARSClientExecptionProc{$ifdef DelphiXE2_UP} = nil{$endif}); overload; override; + procedure POST( + const AFormUrlEncoded: TMARSParameters; + const ABeforeExecute: TProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AAfterExecute: TMARSClientResponseProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AOnException: TMARSClientExecptionProc{$ifdef DelphiXE2_UP} = nil{$endif}); overload; virtual; + + procedure POSTAsync( + const AFormUrlEncoded: TMARSParameters; + const ABeforeExecute: TProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const ACompletionHandler: TProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AOnException: TMARSClientExecptionProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const ASynchronize: Boolean = True); overload; virtual; + + procedure PUT( + const ABeforeExecute: TProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AAfterExecute: TMARSClientResponseProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AOnException: TMARSClientExecptionProc{$ifdef DelphiXE2_UP} = nil{$endif}); overload; override; + procedure PUT( + const AFormUrlEncoded: TMARSParameters; + const ABeforeExecute: TProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AAfterExecute: TMARSClientResponseProc{$ifdef DelphiXE2_UP} = nil{$endif}; + const AOnException: TMARSClientExecptionProc{$ifdef DelphiXE2_UP} = nil{$endif}); overload; virtual; + + function ResponseAsJSON: TJSONValue; + function ResponseAs: T; + function ResponseAsArray: TArray; + published + property FormUrlEncoded: TMARSParameters read FFormUrlEncoded write FFormUrlEncoded; + property Response: TMemoryStream read FResponse; + property ResponseAsString; + end; + +const + FORM_URL_ENCODED_SEPARATOR = '-'; + +implementation + +uses + MARS.Core.MediaType +; + +{ TMARSClientResourceFormUrlEncoded } + +procedure TMARSClientResourceFormUrlEncoded.AfterPOST(const AContent: TStream); +begin + inherited; + AContent.Position := 0; + FResponse.Size := 0; // clear + FResponse.CopyFrom(AContent, 0); +end; + +procedure TMARSClientResourceFormUrlEncoded.AfterPUT(const AContent: TStream); +begin + inherited; + AContent.Position := 0; + FResponse.Size := 0; // clear + FResponse.CopyFrom(AContent, 0); +end; + +procedure TMARSClientResourceFormUrlEncoded.AssignTo(Dest: TPersistent); +begin + inherited; + if Dest is TMARSClientResourceFormUrlEncoded then + TMARSClientResourceFormUrlEncoded(Dest).FFormUrlEncoded := FFormUrlEncoded; +end; + +constructor TMARSClientResourceFormUrlEncoded.Create(AOwner: TComponent); +begin + inherited; + FFormUrlEncoded := TMARSParameters.Create('keys'); + FResponse := TMemoryStream.Create; + SpecificContentType := TMediaType.APPLICATION_FORM_URLENCODED_TYPE; +end; + +destructor TMARSClientResourceFormUrlEncoded.Destroy; +begin + FResponse.Free; + FFormUrlEncoded.Free; + inherited; +end; + +function TMARSClientResourceFormUrlEncoded.GetResponseAsString: string; +begin + Result := StreamToString(FResponse); +end; + +procedure TMARSClientResourceFormUrlEncoded.POST(const AFormUrlEncoded: TMARSParameters; + const ABeforeExecute: TProc; const AAfterExecute: TMARSClientResponseProc; + const AOnException: TMARSClientExecptionProc); +begin + FFormUrlEncoded := AFormUrlEncoded; + POST(ABeforeExecute, AAfterExecute, AOnException); +end; + +procedure TMARSClientResourceFormUrlEncoded.POSTAsync(const AFormUrlEncoded: TMARSParameters; + const ABeforeExecute: TProc; const ACompletionHandler: TProc; + const AOnException: TMARSClientExecptionProc; const ASynchronize: Boolean); +begin + FFormUrlEncoded := AFormUrlEncoded; + inherited POSTAsync(ABeforeExecute, ACompletionHandler, AOnException, ASynchronize); +end; + +procedure TMARSClientResourceFormUrlEncoded.PUT(const AFormUrlEncoded: TMARSParameters; + const ABeforeExecute: TProc; const AAfterExecute: TMARSClientResponseProc; + const AOnException: TMARSClientExecptionProc); +begin + FFormUrlEncoded := AFormUrlEncoded; + PUT(ABeforeExecute, AAfterExecute, AOnException); +end; + +function TMARSClientResourceFormUrlEncoded.ResponseAs: T; +var + LJSON: TJSONValue; +begin + LJSON := ResponseAsJSON; + try + Result := (LJSON as TJSONObject).ToRecord; + finally + LJSON.Free; + end; +end; + +function TMARSClientResourceFormUrlEncoded.ResponseAsArray: TArray; +var + LJSON: TJSONValue; +begin + LJSON := ResponseAsJSON; + try + Result := (LJSON as TJSONArray).ToArrayOfRecord; + finally + LJSON.Free; + end; +end; + +function TMARSClientResourceFormUrlEncoded.ResponseAsJSON: TJSONValue; +begin + Result := StreamToJSONValue(Response); +end; + +procedure TMARSClientResourceFormUrlEncoded.PUT(const ABeforeExecute: TProc; + const AAfterExecute: TMARSClientResponseProc; const AOnException: TMARSClientExecptionProc); +var + LResponseStream: TMemoryStream; +begin + // inherited (!) + + try + BeforePUT(nil); + + if Assigned(ABeforeExecute) then + ABeforeExecute(nil); + + LResponseStream := TMemoryStream.Create; + try + Client.Put(URL, FFormUrlEncoded, LResponseStream, AuthToken, Accept, ContentType); + + AfterPUT(LResponseStream); + + if Assigned(AAfterExecute) then + AAfterExecute(LResponseStream); + finally + LResponseStream.Free; + end; + except + on E:Exception do + begin + if Assigned(AOnException) then + AOnException(E) + else + DoError(E, TMARSHttpVerb.Put, AAfterExecute); + end; + end; +end; + +procedure TMARSClientResourceFormUrlEncoded.POST(const ABeforeExecute: TProc; + const AAfterExecute: TMARSClientResponseProc; const AOnException: TMARSClientExecptionProc); +var + LResponseStream: TMemoryStream; +begin + // inherited (!) + + try + BeforePOST(nil); + + if Assigned(ABeforeExecute) then + ABeforeExecute(nil); + + LResponseStream := TMemoryStream.Create; + try + Client.Post(URL, FFormUrlEncoded, LResponseStream, AuthToken, Accept, ContentType); + + AfterPOST(LResponseStream); + + if Assigned(AAfterExecute) then + AAfterExecute(LResponseStream); + finally + LResponseStream.Free; + end; + except + on E:Exception do + begin + if Assigned(AOnException) then + AOnException(E) + else + DoError(E, TMARSHttpVerb.Post, AAfterExecute); + end; + end; +end; + +end. diff --git a/Source/MARS.Core.Activation.InjectionService.pas b/Source/MARS.Core.Activation.InjectionService.pas index 5b4142a2..99fb9004 100644 --- a/Source/MARS.Core.Activation.InjectionService.pas +++ b/Source/MARS.Core.Activation.InjectionService.pas @@ -10,11 +10,9 @@ interface uses - Classes, SysUtils, Rtti - , MARS.Core.Injection - , MARS.Core.Injection.Interfaces - , MARS.Core.Injection.Types - , MARS.Core.Activation.Interfaces + Classes, SysUtils, System.Rtti, System.TypInfo +, MARS.Core.Injection, MARS.Core.Injection.Interfaces, MARS.Core.Injection.Types +, MARS.Core.Activation.Interfaces ; type @@ -27,13 +25,9 @@ TMARSActivationInjectionService = class(TInterfacedObject, IMARSInjectionServi implementation uses - MARS.Rtti.Utils - , MARS.Core.Token, MARS.Core.URL, MARS.Core.Engine, MARS.Core.Application, MARS.Core.Attributes -{$ifdef DelphiXE6_UP} - , Web.HttpApp -{$else} - , HttpApp -{$endif} + MARS.Rtti.Utils +, MARS.Core.Token, MARS.Core.URL, MARS.Core.Engine, MARS.Core.Application, MARS.Core.Attributes +, MARS.Core.RequestAndResponse.Interfaces ; { TMARSActivationInjectionService } @@ -63,10 +57,10 @@ procedure TMARSActivationInjectionService.GetValue(const ADestination: TRttiObje end ) then AValue := LValue - else if (LType.IsObjectOfType(TWebRequest)) then - AValue := TInjectionValue.Create(AActivation.Request, True) - else if (LType.IsObjectOfType(TWebResponse)) then - AValue := TInjectionValue.Create(AActivation.Response, True) + else if (LType is TRttiInterfaceType) and (LType.Handle = TypeInfo(IMARSRequest)) then + AValue := TInjectionValue.Create(TValue.From(AActivation.Request), True) + else if (LType is TRttiInterfaceType) and (LType.Handle = TypeInfo(IMARSResponse)) then + AValue := TInjectionValue.Create(TValue.From(AActivation.Response), True) else if (LType.IsObjectOfType(TMARSURL)) then AValue := TInjectionValue.Create(AActivation.URL, True) else if (LType.IsObjectOfType(TMARSEngine)) then @@ -96,8 +90,8 @@ procedure RegisterServices; Result := ADestination.HasAttribute or ADestination.HasAttribute - or LType.IsObjectOfType(TWebRequest) - or LType.IsObjectOfType(TWebResponse) + or (LType.Handle = TypeInfo(IMARSRequest)) + or (LType.Handle = TypeInfo(IMARSResponse)) or LType.IsObjectOfType(TMARSURL) or LType.IsObjectOfType(TMARSEngine) or LType.IsObjectOfType(TMARSApplication) diff --git a/Source/MARS.Core.Activation.Interfaces.pas b/Source/MARS.Core.Activation.Interfaces.pas index 3f23fb19..0ea46b42 100644 --- a/Source/MARS.Core.Activation.Interfaces.pas +++ b/Source/MARS.Core.Activation.Interfaces.pas @@ -11,18 +11,11 @@ interface uses SysUtils, Classes, Generics.Collections, Rtti, Diagnostics - , HTTPApp - - , MARS.Core.URL - , MARS.Core.Application - , MARS.Core.Engine - , MARS.Core.Token - , MARS.Core.MediaType - , MARS.Core.Injection.Types - ; +, MARS.Core.URL, MARS.Core.Application, MARS.Core.Engine, MARS.Core.Token +, MARS.Core.MediaType, MARS.Core.Injection.Types, MARS.Core.RequestAndResponse.Interfaces +; type - IMARSActivation = interface procedure AddToContext(AValue: TValue); function HasToken: Boolean; @@ -40,11 +33,11 @@ interface function GetMethodArguments: TArray; function GetMethodAttributes: TArray; function GetMethodResult: TValue; - function GetRequest: TWebRequest; + function GetRequest: IMARSRequest; function GetResource: TRttiType; function GetResourceAttributes: TArray; function GetResourceInstance: TObject; - function GetResponse: TWebResponse; + function GetResponse: IMARSResponse; function GetURL: TMARSURL; function GetURLPrototype: TMARSURL; function GetToken: TMARSToken; @@ -60,11 +53,11 @@ interface property MethodArguments: TArray read GetMethodArguments; property MethodAttributes: TArray read GetMethodAttributes; property MethodResult: TValue read GetMethodResult; - property Request: TWebRequest read GetRequest; + property Request: IMARSRequest read GetRequest; property Resource: TRttiType read GetResource; property ResourceAttributes: TArray read GetResourceAttributes; property ResourceInstance: TObject read GetResourceInstance; - property Response: TWebResponse read GetResponse; + property Response: IMARSResponse read GetResponse; property URL: TMARSURL read GetURL; property URLPrototype: TMARSURL read GetURLPrototype; property Token: TMARSToken read GetToken; diff --git a/Source/MARS.Core.Activation.pas b/Source/MARS.Core.Activation.pas index 2e42fede..762a66bd 100644 --- a/Source/MARS.Core.Activation.pas +++ b/Source/MARS.Core.Activation.pas @@ -10,12 +10,12 @@ interface uses - SysUtils, Classes, Generics.Collections, Rtti, Diagnostics, HTTPApp + SysUtils, Classes, Generics.Collections, Rtti, Diagnostics , MARS.Core.Classes, MARS.Core.URL, MARS.Core.MediaType , MARS.Core.Application, MARS.Core.Engine, MARS.Core.Token , MARS.Core.Registry.Utils, MARS.Core.Injection.Types, MARS.Core.Activation.Interfaces -, MARS.Core.MessageBodyWriter +, MARS.Core.MessageBodyWriter, MARS.Core.RequestAndResponse.Interfaces ; type @@ -23,7 +23,7 @@ TMARSActivation = class; TMARSActivationFactoryFunc = reference to function (const AEngine: TMARSEngine; const AApplication: TMARSApplication; - const ARequest: TWebRequest; const AResponse: TWebResponse; + const ARequest: IMARSRequest; const AResponse: IMARSResponse; const AURL: TMARSURL ): IMARSActivation; @@ -43,8 +43,8 @@ TMARSAuthorizationInfo = record TMARSActivation = class(TInterfacedObject, IMARSActivation) private - FRequest: TWebRequest; - FResponse: TWebResponse; + FRequest: IMARSRequest; + FResponse: IMARSResponse; class var FBeforeInvokeProcs: TArray; class var FAfterInvokeProcs: TArray; class var FInvokeErrorProcs: TArray; @@ -97,7 +97,7 @@ TMARSActivation = class(TInterfacedObject, IMARSActivation) procedure CheckAuthorization; virtual; public constructor Create(const AEngine: TMARSEngine; const AApplication: TMARSApplication; - const ARequest: TWebRequest; const AResponse: TWebResponse; const AURL: TMARSURL); virtual; + const ARequest: IMARSRequest; const AResponse: IMARSResponse; const AURL: TMARSURL); virtual; destructor Destroy; override; // --- IMARSActivation implementation -------------- @@ -116,11 +116,11 @@ TMARSActivation = class(TInterfacedObject, IMARSActivation) function GetMethodArguments: TArray; inline; function GetMethodAttributes: TArray; inline; function GetMethodResult: TValue; inline; - function GetRequest: TWebRequest; inline; + function GetRequest: IMARSRequest; inline; function GetResource: TRttiType; inline; function GetResourceAttributes: TArray; inline; function GetResourceInstance: TObject; inline; - function GetResponse: TWebResponse; inline; + function GetResponse: IMARSResponse; inline; function GetURL: TMARSURL; inline; function GetURLPrototype: TMARSURL; inline; function GetToken: TMARSToken; inline; @@ -131,10 +131,10 @@ TMARSActivation = class(TInterfacedObject, IMARSActivation) property InvocationTime: TStopwatch read FInvocationTime; property Method: TRttiMethod read FMethod; property MethodArguments: TArray read FMethodArguments; - property Request: TWebRequest read FRequest; + property Request: IMARSRequest read FRequest; property Resource: TRttiType read FResource; property ResourceInstance: TObject read FResourceInstance; - property Response: TWebResponse read FResponse; + property Response: IMARSResponse read FResponse; property URL: TMARSURL read FURL; property URLPrototype: TMARSURL read FURLPrototype; property Token: TMARSToken read GetToken; @@ -152,7 +152,7 @@ TMARSActivation = class(TInterfacedObject, IMARSActivation) class var CreateActivationFunc: TMARSActivationFactoryFunc; class function CreateActivation(const AEngine: TMARSEngine; const AApplication: TMARSApplication; - const ARequest: TWebRequest; const AResponse: TWebResponse; + const ARequest: IMARSRequest; const AResponse: IMARSResponse; const AURL: TMARSURL): IMARSActivation; end; @@ -208,7 +208,7 @@ function TMARSActivation.GetMethodReturnType: TRttiType; Result := FMethodReturnType; end; -function TMARSActivation.GetRequest: TWebRequest; +function TMARSActivation.GetRequest: IMARSRequest; begin Result := FRequest; end; @@ -228,7 +228,7 @@ function TMARSActivation.GetResourceInstance: TObject; Result := FResourceInstance; end; -function TMARSActivation.GetResponse: TWebResponse; +function TMARSActivation.GetResponse: IMARSResponse; begin Result := FResponse; end; @@ -563,7 +563,7 @@ procedure TMARSActivation.SetCustomHeaders; LCustomAtributeProcessor := procedure (ACustomHeader: CustomHeaderAttribute) begin - Response.CustomHeaders.Values[ACustomHeader.HeaderName] := ACustomHeader.Value; + Response.SetHeader(ACustomHeader.HeaderName, ACustomHeader.Value); end; TRttiHelper.ForEachAttribute(FResourceAttributes, LCustomAtributeProcessor); TRttiHelper.ForEachAttribute(FMethodAttributes, LCustomAtributeProcessor); @@ -606,7 +606,7 @@ procedure TMARSActivation.Invoke; begin try try - Request.ReadTotalContent; // workaround for https://quality.embarcadero.com/browse/RSP-14674 + Request.CheckWorkaroundForISAPI; // setup phase FSetupTime := TStopWatch.StartNew; @@ -741,7 +741,7 @@ function TMARSActivation.GetInvocationTime: TStopwatch; constructor TMARSActivation.Create(const AEngine: TMARSEngine; const AApplication: TMARSApplication; - const ARequest: TWebRequest; const AResponse: TWebResponse; + const ARequest: IMARSRequest; const AResponse: IMARSResponse; const AURL: TMARSURL); begin inherited Create; @@ -775,8 +775,8 @@ constructor TMARSActivation.Create(const AEngine: TMARSEngine; end; class function TMARSActivation.CreateActivation(const AEngine: TMARSEngine; - const AApplication: TMARSApplication; const ARequest: TWebRequest; - const AResponse: TWebResponse; const AURL: TMARSURL): IMARSActivation; + const AApplication: TMARSApplication; const ARequest: IMARSRequest; + const AResponse: IMARSResponse; const AURL: TMARSURL): IMARSActivation; begin if Assigned(CreateActivationFunc) then Result := CreateActivationFunc(AEngine, AApplication, ARequest, AResponse, AURL) diff --git a/Source/MARS.Core.Attributes.pas b/Source/MARS.Core.Attributes.pas index 69382af3..ab242a94 100644 --- a/Source/MARS.Core.Attributes.pas +++ b/Source/MARS.Core.Attributes.pas @@ -10,14 +10,11 @@ interface uses - Classes, SysUtils, RTTI, Generics.Collections - , HttpApp, Web.ReqMulti - , MARS.Core.Declarations - , MARS.Core.Utils - , MARS.Core.URL - , MARS.Core.JSON - , MARS.Core.Activation.Interfaces - , MARS.Core.Exceptions + Classes, SysUtils, System.Rtti, System.TypInfo, Generics.Collections +, Web.ReqMulti +, MARS.Core.Declarations, MARS.Core.Utils, MARS.Core.URL, MARS.Core.JSON +, MARS.Core.Activation.Interfaces, MARS.Core.RequestAndResponse.Interfaces +, MARS.Core.Exceptions ; type @@ -36,48 +33,48 @@ HttpMethodAttribute = class(MARSAttribute) protected function GetHttpMethodName: string; virtual; public - function Matches(const ARequest: TWebRequest): Boolean; virtual; + function Matches(const ARequest: IMARSRequest): Boolean; virtual; property HttpMethodName: string read GetHttpMethodName; end; - ANYAttribute = class(HttpMethodAttribute) - public - function Matches(const ARequest: TWebRequest): Boolean; override; - end; +// ANYAttribute = class(HttpMethodAttribute) +// public +// function Matches(const ARequest: IMARSRequest): Boolean; override; +// end; GETAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; POSTAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; PUTAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; DELETEAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; PATCHAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; HEADAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; OPTIONSAttribute = class(HttpMethodAttribute) public - function Matches(const ARequest: TWebRequest): Boolean; override; + function Matches(const ARequest: IMARSRequest): Boolean; override; end; ConsumesAttribute = class(MARSAttribute) @@ -466,59 +463,51 @@ function HttpMethodAttribute.GetHttpMethodName: string; {$endif} end; -function HttpMethodAttribute.Matches(const ARequest: TWebRequest): Boolean; +function HttpMethodAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin Result := False; end; { GETAttribute } -function GETAttribute.Matches(const ARequest: TWebRequest): Boolean; +function GETAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin - Result := ARequest.MethodType = TMethodType.mtGet; + Result := SameText(ARequest.Method, 'GET'); end; { POSTAttribute } -function POSTAttribute.Matches(const ARequest: TWebRequest): Boolean; +function POSTAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin - Result := ARequest.MethodType = TMethodType.mtPost; + Result := SameText(ARequest.Method, 'POST'); end; { PUTAttribute } -function PUTAttribute.Matches(const ARequest: TWebRequest): Boolean; +function PUTAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin - Result := ARequest.MethodType = TMethodType.mtPut; + Result := SameText(ARequest.Method, 'PUT'); end; { DELETEAttribute } -function DELETEAttribute.Matches(const ARequest: TWebRequest): Boolean; +function DELETEAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin -{$ifdef DelphiXE7_UP} - Result := ARequest.MethodType = TMethodType.mtDelete; -{$else} - Result := SameText(string(ARequest.Method), 'Delete'); -{$endif} + Result := SameText(ARequest.Method, 'DELETE'); end; { PATCHAttribute } -function PATCHAttribute.Matches(const ARequest: TWebRequest): Boolean; +function PATCHAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin -{$ifdef DelphiXE7_UP} - Result := ARequest.MethodType = TMethodType.mtPatch; -{$else} - Result := SameText(string(ARequest.Method), 'Patch'); -{$endif} + Result := SameText(ARequest.Method, 'PATCH'); end; { HEADAttribute } -function HEADAttribute.Matches(const ARequest: TWebRequest): Boolean; +function HEADAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin - Result := ARequest.MethodType = TMethodType.mtHead; + Result := SameText(ARequest.Method, 'HEAD'); end; { CustomHeaderAttribute } @@ -597,7 +586,7 @@ function QueryParamAttribute.GetValue(const ADestination: TRttiObject; LReader: IMessageBodyReader; LIndex: Integer; begin - LIndex := AActivation.Request.QueryFields.IndexOfName(GetActualName(ADestination)); + LIndex := AActivation.Request.GetQueryParamIndex(GetActualName(ADestination)); if (LIndex = -1) then CheckRequiredAttribute(ADestination) else @@ -608,7 +597,7 @@ function QueryParamAttribute.GetValue(const ADestination: TRttiObject; try Result := LReader.ReadFrom( {$ifdef Delphi10Berlin_UP} TEncoding.UTF8.GetBytes( {$endif} - AActivation.Request.QueryFields.ValueFromIndex[LIndex] + AActivation.Request.GetQueryParamValue(LIndex) {$ifdef Delphi10Berlin_UP} ) {$endif} , ADestination, LMediaType, AActivation); finally @@ -617,7 +606,7 @@ function QueryParamAttribute.GetValue(const ADestination: TRttiObject; else // 2 - fallback (raw) begin Result := StringToTValue( - AActivation.Request.QueryFields.ValueFromIndex[LIndex] + AActivation.Request.GetQueryParamValue(LIndex) , ADestination.GetRttiType ); end; @@ -636,23 +625,12 @@ function FormParamAttribute.GetValue(const ADestination: TRttiObject; var LMediaType: TMediaType; LReader: IMessageBodyReader; - LParamIndex: Integer; - LFileIndex: Integer; - LIndex: Integer; - LFile: TAbstractWebRequestFile; + LParamIndex, LFileIndex: Integer; + LActualName: string; begin - LParamIndex := AActivation.Request.ContentFields.IndexOfName(GetActualName(ADestination)); - LFileIndex := -1; - for LIndex := 0 to AActivation.Request.Files.Count-1 do - begin - LFile := AActivation.Request.Files[LIndex]; - if SameText(LFile.FieldName, GetActualName(ADestination)) then - begin - LFileIndex := LIndex; - Break; - end; - end; - + LActualName := GetActualName(ADestination); + LParamIndex := AActivation.Request.GetFormParamIndex(LActualName); + LFileIndex := AActivation.Request.GetFormFileParamIndex(LActualName); if (LParamIndex = -1) and (LFileIndex = -1) then CheckRequiredAttribute(ADestination) else @@ -661,17 +639,23 @@ function FormParamAttribute.GetValue(const ADestination: TRttiObject; TMARSMessageBodyReaderRegistry.Instance.FindReader(ADestination, LReader, LMediaType); if Assigned(LReader) then try - Result := LReader.ReadFrom( - {$ifdef Delphi10Berlin_UP} TEncoding.UTF8.GetBytes( {$endif} - AActivation.Request.ContentFields.ValueFromIndex[LParamIndex] - {$ifdef Delphi10Berlin_UP} ) {$endif} - , ADestination, LMediaType, AActivation); + if LParamIndex <> -1 then + Result := LReader.ReadFrom( + {$ifdef Delphi10Berlin_UP} TEncoding.UTF8.GetBytes( {$endif} + AActivation.Request.GetFormParamValue(LParamIndex) + {$ifdef Delphi10Berlin_UP} ) {$endif} + , ADestination, LMediaType, AActivation) + else if LFileIndex <> -1 then + Result := LReader.ReadFrom( + {$ifdef Delphi10Berlin_UP} TEncoding.UTF8.GetBytes( {$endif} + '' + {$ifdef Delphi10Berlin_UP} ) {$endif} + , ADestination, LMediaType, AActivation); finally FreeAndNil(LMediaType); end else // 2 - fallback (raw) - Result := StringToTValue(AActivation.Request.ContentFields.ValueFromIndex[LParamIndex] - , ADestination.GetRttiType); + Result := StringToTValue(AActivation.Request.GetFormParamValue(LParamIndex), ADestination.GetRttiType); end; end; @@ -692,7 +676,7 @@ function HeaderParamAttribute.GetValue(const ADestination: TRttiObject; LValue: string; begin Result := TValue.Empty; - LValue := AActivation.Request.GetFieldByName(TheName); + LValue := AActivation.Request.GetHeaderParamValue(TheName); if (LValue = '') then CheckRequiredAttribute(TheDestination); @@ -756,11 +740,11 @@ function CookieParamAttribute.GetValue(const ADestination: TRttiObject; var LIndex: Integer; begin - LIndex := AActivation.Request.CookieFields.IndexOfName(GetActualName(ADestination)); + LIndex := AActivation.Request.GetCookieParamIndex(GetActualName(ADestination)); if LIndex = -1 then CheckRequiredAttribute(ADestination); Result := StringToTValue( - AActivation.Request.CookieFields.ValueFromIndex[LIndex] + AActivation.Request.GetCookieParamValue(LIndex) , ADestination.GetRttiType ); end; @@ -821,16 +805,16 @@ function AuthorizationAttribute.ToString: string; Result := Result.Substring(0, Result.Length - 'Attribute'.Length); end; -{ ANYAttribute } - -function ANYAttribute.Matches(const ARequest: TWebRequest): Boolean; -begin - Result := ARequest.MethodType = TMethodType.mtAny; -end; +//{ ANYAttribute } +// +//function ANYAttribute.Matches(const ARequest: IMARSRequest): Boolean; +//begin +// Result := SameText(ARequest.Method, 'ANY'); +//end; { OPTIONSAttribute } -function OPTIONSAttribute.Matches(const ARequest: TWebRequest): Boolean; +function OPTIONSAttribute.Matches(const ARequest: IMARSRequest): Boolean; begin Result := SameText(ARequest.Method, 'OPTIONS'); end; @@ -859,7 +843,7 @@ function FormParamsAttribute.GetValue(const ADestination: TRttiObject; LMediaType: TMediaType; LReader: IMessageBodyReader; begin - if AActivation.Request.ContentFields.Count = 0 then + if AActivation.Request.GetFormParamCount = 0 then CheckRequiredAttribute(ADestination) else begin @@ -874,7 +858,7 @@ function FormParamsAttribute.GetValue(const ADestination: TRttiObject; FreeAndNil(LMediaType); end else // 2 - fallback (raw) - Result := StringToTValue(AActivation.Request.ContentFields.Text, ADestination.GetRttiType); + Result := StringToTValue(AActivation.Request.GetFormParams, ADestination.GetRttiType); end; end; diff --git a/Source/MARS.Core.Engine.pas b/Source/MARS.Core.Engine.pas index 17be5cc6..89c5c330 100644 --- a/Source/MARS.Core.Engine.pas +++ b/Source/MARS.Core.Engine.pas @@ -10,15 +10,11 @@ interface uses - SysUtils, HTTPApp, Classes, Generics.Collections + SysUtils, Classes, Generics.Collections , SyncObjs - , MARS.Core.Classes - , MARS.Core.Registry - , MARS.Core.Application - , MARS.Core.URL - , MARS.Core.Exceptions - , MARS.Utils.Parameters + , MARS.Core.Classes, MARS.Core.Registry, MARS.Core.Application, MARS.Core.URL + , MARS.Core.Exceptions, MARS.Utils.Parameters, MARS.Core.RequestAndResponse.Interfaces ; {$M+} @@ -31,10 +27,10 @@ EMARSEngineException = class(EMARSHttpException); TMARSEngine = class; TBeforeHandleRequestProc = reference to function(const AEngine: TMARSEngine; - const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; + const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; var Handled: Boolean): Boolean; TAfterHandleRequestProc = reference to procedure(const AEngine: TMARSEngine; - const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; + const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; var Handled: Boolean); TMARSEngine = class @@ -52,12 +48,12 @@ TMARSEngine = class procedure SetBasePath(const Value: string); virtual; procedure SetPort(const Value: Integer); virtual; procedure SetThreadPoolSize(const Value: Integer); virtual; - procedure PatchCORS(const ARequest: TWebRequest; const AResponse: TWebResponse); virtual; + procedure PatchCORS(const ARequest: IMARSRequest; const AResponse: IMARSResponse); virtual; public constructor Create(const AName: string = DEFAULT_ENGINE_NAME); virtual; destructor Destroy; override; - function HandleRequest(ARequest: TWebRequest; AResponse: TWebResponse): Boolean; virtual; + function HandleRequest(ARequest: IMARSRequest; AResponse: IMARSResponse): Boolean; virtual; function AddApplication(const AName, ABasePath: string; const AResources: array of string; const AParametersSliceName: string = ''): TMARSApplication; virtual; @@ -190,7 +186,7 @@ procedure TMARSEngine.EnumerateApplications( end; end; -function TMARSEngine.HandleRequest(ARequest: TWebRequest; AResponse: TWebResponse): Boolean; +function TMARSEngine.HandleRequest(ARequest: IMARSRequest; AResponse: IMARSResponse): Boolean; var LApplication: TMARSApplication; LURL: TMARSURL; @@ -240,13 +236,12 @@ function TMARSEngine.HandleRequest(ARequest: TWebRequest; AResponse: TWebRespons end; end; -procedure TMARSEngine.PatchCORS(const ARequest: TWebRequest; - const AResponse: TWebResponse); +procedure TMARSEngine.PatchCORS(const ARequest: IMARSRequest; + const AResponse: IMARSResponse); procedure SetHeaderFromParameter(const AHeader, AParamName, ADefault: string); begin - AResponse.CustomHeaders.Values[AHeader] := - Parameters.ByName(AParamName, ADefault).AsString; + AResponse.SetHeader(AHeader, Parameters.ByName(AParamName, ADefault).AsString); end; begin diff --git a/Source/MARS.Core.JSON.pas b/Source/MARS.Core.JSON.pas index 7177ef7e..4bf1c5e9 100644 --- a/Source/MARS.Core.JSON.pas +++ b/Source/MARS.Core.JSON.pas @@ -1,1007 +1,1010 @@ -(* - Copyright 2016, MARS-Curiosity library - - Home: https://github.com/andrea-magni/MARS -*) -unit MARS.Core.JSON; - -{$I MARS.inc} - -interface - -uses -{$IFDEF Delphi10Rio_UP} - Generics.Collections, -{$ENDIF} -{$ifdef DelphiXE6_UP} - JSON -{$else} - DBXJSON -{$endif} - , SysUtils -{$ifdef DelphiXE2_UP} - , System.Rtti -{$else} - , Rtti -{$endif} - , TypInfo, REST.JSON -; - -type - TJSONAncestor = {$ifdef DelphiXE6_UP}JSON.TJSONAncestor{$else}DBXJSON.TJSONAncestor{$endif}; - TJSONPair = {$ifdef DelphiXE6_UP}JSON.TJSONPair{$else}DBXJSON.TJSONPair{$endif}; - TJSONValue = {$ifdef DelphiXE6_UP}JSON.TJSONValue{$else}DBXJSON.TJSONValue{$endif}; - TJSONTrue = {$ifdef DelphiXE6_UP}JSON.TJSONTrue{$else}DBXJSON.TJSONTrue{$endif}; - TJSONString = {$ifdef DelphiXE6_UP}JSON.TJSONString{$else}DBXJSON.TJSONString{$endif}; - TJSONNumber = {$ifdef DelphiXE6_UP}JSON.TJSONNumber{$else}DBXJSON.TJSONNumber{$endif}; - TJSONObject = {$ifdef DelphiXE6_UP}JSON.TJSONObject{$else}DBXJSON.TJSONObject{$endif}; - TJSONNull = {$ifdef DelphiXE6_UP}JSON.TJSONNull{$else}DBXJSON.TJSONNull{$endif}; - TJSONFalse = {$ifdef DelphiXE6_UP}JSON.TJSONFalse{$else}DBXJSON.TJSONFalse{$endif}; - TJSONArray = {$ifdef DelphiXE6_UP}JSON.TJSONArray{$else}DBXJSON.TJSONArray{$endif}; - - TJSONValueHelper = class helper for TJSONValue - public -{$ifndef DelphiXE7_UP} - function TryGetValue(const APath: string; out AValue: T): Boolean; overload; - function ToJSON: string; -{$endif} - end; - - JSONNameAttribute = class(TCustomAttribute) - private - FName: string; - public - constructor Create(const AName: string); - property Name: string read FName; - end; - - TToRecordFilterProc = reference to procedure (const AField: TRttiField; - const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean); - - TToJSONFilterProc = reference to procedure (const AField: TRttiField; - const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean); - - TJSONRawString = type string; - -{$ifndef DelphiXE6_UP} - TJSONArrayEnumerator = class - private - FIndex: Integer; - FArray: TJSONArray; - public - constructor Create(const AArray: TJSONArray); - function GetCurrent: TJSONValue; inline; - function MoveNext: Boolean; - property Current: TJSONValue read GetCurrent; - end; -{$endif} - - TJSONArrayHelper= class helper for TJSONArray - private - {$ifndef DelphiXE6_UP} - function GetCount: Integer; inline; - function GetValue(const Index: Integer): TJSONValue; inline; - {$endif} - public - function ToArrayOfRecord(): TArray; - procedure FromArrayOfRecord(const AArray: TArray; - const AFilterProc: TToJSONFilterProc = nil); - procedure FromArrayOfObject(const AArray: TArray; - const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]); - function ForEach(const AFunc: TFunc): Integer; - - {$ifndef DelphiXE6_UP} - function GetEnumerator: TJSONArrayEnumerator; - - property Count: Integer read GetCount; - property Items[const Index: Integer]: TJSONValue read GetValue; - {$endif} - - class function ArrayOfRecordToJSON(const AArray: TArray; const AFilterProc: TToJSONFilterProc = nil): TJSONArray; - class function ArrayOfObjectToJSON(const AArray: TArray): TJSONArray; - end; - - TJSONObjectHelper = class helper(TJSONValueHelper) for TJSONObject - private -{$ifndef DelphiXE6_UP} - function GetCount: Integer; inline; - function GetPair(const Index: Integer): TJSONPair; inline; -{$endif} - function GetExactPairName(const ACaseInsensitiveName: string): string; - public - function ReadStringValue(const AName: string; const ADefault: string = ''): string; - function ReadIntegerValue(const AName: string; const ADefault: Integer = 0): Integer; -{$ifdef DelphiXE6_UP} - function ReadInt64Value(const AName: string; const ADefault: Int64 = 0): Int64; -{$endif} - function ReadDoubleValue(const AName: string; const ADefault: Double = 0.0): Double; - function ReadBoolValue(const AName: string; const ADefault: Boolean = False): Boolean; - function ReadDateTimeValue(const AName: string; const ADefault: TDateTime = 0.0; - const AReturnUTC: Boolean = False): TDateTime; - function ReadUnixTimeValue(const AName: string; const ADefault: TDateTime = 0.0): TDateTime; - function ReadValue(const AName: string; const ADefault: TValue; - const ADesiredType: TRttiType; const ANameCaseSensitive: Boolean = True): TValue; overload; - function ReadValue(const AName: string; const ADesiredType: TRttiType; - const ANameCaseSensitive: Boolean; out AValue: TValue): Boolean; overload; - function ReadArrayValue(const AName: string): TJSONArray; overload; inline; - function ReadArrayValue(const AName: string): TArray; overload; inline; - - procedure WriteStringValue(const AName: string; const AValue: string); - procedure WriteIntegerValue(const AName: string; const AValue: Integer); - procedure WriteInt64Value(const AName: string; const AValue: Int64); - procedure WriteDoubleValue(const AName: string; const AValue: Double); - procedure WriteBoolValue(const AName: string; const AValue: Boolean); - procedure WriteDateTimeValue(const AName: string; const AValue: TDateTime; - const AInputIsUTC: Boolean = False); - procedure WriteUnixTimeValue(const AName: string; const AValue: TDateTime); - procedure WriteTValue(const AName: string; const AValue: TValue); - procedure WriteArrayValue(const AName: string; const AArray: TJSONArray); overload; inline; - procedure WriteArrayValue(const AName: string; const AArray: TArray); overload; inline; - - procedure FromRecord(ARecord: T; const AFilterProc: TToJSONFilterProc = nil); overload; - procedure FromRecord(const ARecord: TValue; const AFilterProc: TToJSONFilterProc = nil); overload; - function ToRecord(const AFilterProc: TToRecordFilterProc = nil): T; overload; - function ToRecord(const ARecordType: TRttiType; - const AFilterProc: TToRecordFilterProc = nil): TValue; overload; - -{$ifndef DelphiXE6_UP} - property Count: Integer read GetCount; - property Pairs[const Index: Integer]: TJSONPair read GetPair; -{$endif} - - class function ObjectToJSON(const AObject: TObject; - const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): TJSONObject; overload; - - class function JSONToObject(const AJSON: TJSONObject; - const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): T; overload; - - class function JSONToObject(const AClassType: TClass; const AJSON: TJSONObject; - const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): TObject; overload; - - class function RecordToJSON(ARecord: T; - const AFilterProc: TToJSONFilterProc = nil): TJSONObject; overload; - class function RecordToJSON(const ARecord: TValue; - const AFilterProc: TToJSONFilterProc = nil): TJSONObject; overload; - - class function JSONToRecord(const AJSON: TJSONObject; - const AFilterProc: TToRecordFilterProc = nil): T; overload; - class function JSONToRecord(const ARecordType: TRttiType; const AJSON: TJSONObject; - const AFilterProc: TToRecordFilterProc = nil): TValue; overload; - - class function TValueToJSONValue(const AValue: TValue): TJSONValue; - class function TJSONValueToTValue(const AValue: TJSONValue; const ADesiredType: TRttiType): TValue; - end; - - function StringArrayToJsonArray(const AStringArray: TArray): TJSONArray; - function JsonArrayToStringArray(const AJSONArray: TJSONArray): TArray; - -implementation - -uses - DateUtils, Variants, StrUtils - , MARS.Core.Utils - , MARS.Rtti.Utils -; - -class function TJSONObjectHelper.TValueToJSONValue( - const AValue: TValue): TJSONValue; -var - LArray: TJSONArray; - LIndex: Integer; -begin - if AValue.IsEmpty and not AValue.IsArray then - Result := TJSONNull.Create - - else if (AValue.Kind in [tkString, tkUString, tkChar, {$ifdef DelphiXE6_UP} tkWideChar, {$endif} tkLString, tkWString]) then - Result := TJSONString.Create(AValue.AsString) - - else if AValue.IsArray then - begin - LArray := TJSONArray.Create; - try - for LIndex := 0 to AValue.GetArrayLength-1 do - LArray.AddElement(TValueToJSONValue(AValue.GetArrayElement(LIndex))); - - Result := LArray; - except - LArray.Free; - raise; - end; - end - - else if (AValue.Kind in [tkRecord]) then - Result := TJSONObject.RecordToJSON(AValue) - - else if (AValue.IsType) then - Result := BooleanToTJSON(AValue.AsType) - - else if AValue.TypeInfo = TypeInfo(TDateTime) then - Result := TJSONString.Create( DateToJSON(AValue.AsType) ) - else if AValue.TypeInfo = TypeInfo(TDate) then - Result := TJSONString.Create( DateToJSON(AValue.AsType) ) - else if AValue.TypeInfo = TypeInfo(TTime) then - Result := TJSONString.Create( DateToJSON(AValue.AsType) ) - - else if (AValue.Kind in [tkInt64]) then - Result := TJSONNumber.Create( AValue.AsType ) - else if (AValue.Kind in [tkInteger]) then - Result := TJSONNumber.Create( AValue.AsType ) - - else if (AValue.Kind in [tkFloat]) then - Result := TJSONNumber.Create( AValue.AsType ) - - else if (AValue.Kind in [tkVariant]) then - Result := TValueToJSONValue( TValue.FromVariant(AValue.AsVariant) ) - - else if (AValue.IsInstanceOf(TObject)) then - Result := ObjectToJSON(AValue.AsObject) - - else - Result := TJSONString.Create(AValue.ToString); - -end; - -function StringArrayToJsonArray(const AStringArray: TArray): TJSONArray; -var - LIndex: Integer; -begin - Result := TJSONArray.Create; - try - for LIndex := Low(AStringArray) to High(AStringArray) do - Result.Add(AStringArray[LIndex]); - except - Result.Free; - raise; - end; -end; - -function JsonArrayToStringArray(const AJSONArray: TJSONArray): TArray; -var - LElement: TJSONValue; - LIndex: Integer; -begin - SetLength(Result, AJSONArray.Count); - - for LIndex := 0 to AJSONArray.Count-1 do - begin - LElement := AJSONArray.Items[LIndex]; - if LElement is TJSONString then - Result[LIndex] := TJSONString(LElement).Value - else if LElement is TJSONNumber then - Result[LIndex] := TJSONNumber(LElement).ToString - else if LElement is TJSONTrue then - Result[LIndex] := 'true' - else if LElement is TJSONFalse then - Result[LIndex] := 'false' - else - Result[LIndex] := LElement.ToString; - end; -end; - -{ TJSONValueHelper } -{$ifndef DelphiXE7_UP} -function TJSONValueHelper.TryGetValue(const APath: string; - out AValue: T): Boolean; -var - LJSONValue: TJSONValue; - LPair: TJSONPair; -begin - LJSONValue := nil; - if Self is TJSONObject then - begin - LPair := TJSONObject(Self).Get(APath); - if Assigned(LPair) then - LJSONValue := LPair.JsonValue; - end; - Result := LJSONValue <> nil; - if Result then - begin - try - AValue := T(LJSONValue); - except - Result := False; - end; - end; -end; -{$endif} - -{$ifndef DelphiXE7_UP} -function TJSONValueHelper.ToJSON: string; -var - LBytes: TBytes; -begin - SetLength(LBytes, Length(ToString) * 6); - SetLength(LBytes, ToBytes(LBytes, 0)); - Result := TEncoding.Default.GetString(LBytes); -end; -{$endif} - -{ TJSONArrayEnumerator } - -{$ifndef DelphiXE6_UP} -constructor TJSONArrayEnumerator.Create(const AArray: TJSONArray); -begin - inherited Create; - FIndex := -1; - FArray := AArray; -end; - -function TJSONArrayEnumerator.GetCurrent: TJSONValue; -begin - Result := FArray.GetValue(FIndex); -end; - -function TJSONArrayEnumerator.MoveNext: Boolean; -begin - Result := FIndex < FArray.Count - 1; - if Result then - Inc(FIndex); -end; -{$endif} - -{ TJSONArrayHelper } - -function TJSONArrayHelper.ToArrayOfRecord: TArray; -var - LElement: TJSONValue; -begin - Result := []; - for LElement in Self do - Result := Result + [(LElement as TJSONObject).ToRecord()] -end; - -class function TJSONArrayHelper.ArrayOfObjectToJSON( - const AArray: TArray): TJSONArray; -begin - Result := TJSONArray.Create; - try - Result.FromArrayOfObject(AArray); - except - Result.Free; - raise; - end; -end; - -class function TJSONArrayHelper.ArrayOfRecordToJSON(const AArray: TArray; - const AFilterProc: TToJSONFilterProc): TJSONArray; -begin - Result := TJSONArray.Create; - try - Result.FromArrayOfRecord(AArray, AFilterProc); - except - Result.Free; - raise; - end; -end; - -function TJSONArrayHelper.ForEach(const AFunc: TFunc): Integer; -var - LIndex: Integer; - LItem: TJSONValue; -begin - Result := 0; - if not Assigned(AFunc) then - Exit; - for LIndex := 0 to Count-1 do - begin - LItem := Items[Lindex]; - if LItem is T then - begin - if not AFunc(T(LItem)) then - Break; - Inc(Result); - end; - end; -end; - -procedure TJSONArrayHelper.FromArrayOfObject(const AArray: TArray; - const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]); -var - LObject: T; - LObj: TJSONObject; -begin - // clear all - while Count > 0 do - Remove(0); - - for LObject in AArray do - AddElement(TJSONObject.ObjectToJSON(LObject, AOptions)); -end; - -procedure TJSONArrayHelper.FromArrayOfRecord(const AArray: TArray; - const AFilterProc: TToJSONFilterProc); -var - LRecord: T; - LObj: TJSONObject; -begin - // clear all - while Count > 0 do - Remove(0); - - for LRecord in AArray do - begin - LObj := TJSONObject.Create; - try - LObj.FromRecord(LRecord, AFilterProc); - AddElement(LObj); - except - LObj.Free; - raise; - end; - end; -end; - - -{$ifndef DelphiXE6_UP} - -function TJSONArrayHelper.GetCount: Integer; -begin - Result := Size; -end; - -function TJSONArrayHelper.GetEnumerator: TJSONArrayEnumerator; -begin - Result := TJSONArrayEnumerator.Create(Self); -end; - -function TJSONArrayHelper.GetValue(const Index: Integer): TJSONValue; -begin - Result := Get(Index); -end; -{$endif} - -{ TJSONObjectHelper } - -{$ifndef DelphiXE6_UP} -function TJSONObjectHelper.GetCount: Integer; -begin - Result := Size; -end; - -function TJSONObjectHelper.GetPair(const Index: Integer): TJSONPair; -begin - Result := Get(Index); -end; -{$endif} - -function TJSONObjectHelper.GetExactPairName( - const ACaseInsensitiveName: string): string; -var - LIndex: Integer; - LPair: TJSONPair; -begin - Result := ACaseInsensitiveName; - for LIndex := 0 to Count -1 do - begin - LPair := Pairs[LIndex]; - if SameText(LPair.JsonString.Value, ACaseInsensitiveName) then - begin - Result := LPair.JsonString.Value; - Exit; - end; - end; -end; - - -class function TJSONObjectHelper.JSONToObject(const AClassType: TClass; - const AJSON: TJSONObject; const AOptions: TJsonOptions): TObject; -var - LConstructor: TRttiMethod; -begin - Result := nil; - - LConstructor := TRTTIHelper.FindParameterLessConstructor(AClassType); - if not Assigned(LConstructor) then - Exit; - - Result := LConstructor.Invoke(AClassType, []).AsObject; - try - TJson.JsonToObject(Result, AJSON, AOptions); - except - Result.Free; - raise; - end; -end; - -class function TJSONObjectHelper.JSONToObject(const AJSON: TJSONObject; - const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): T; -begin - Result := TJSON.JsonToObject(AJSON, AOptions); -end; - -class function TJSONObjectHelper.JSONToRecord(const ARecordType: TRttiType; - const AJSON: TJSONObject; const AFilterProc: TToRecordFilterProc): TValue; -begin - Assert(Assigned(AJSON)); - Result := AJSON.ToRecord(ARecordType, AFilterProc); -end; - -class function TJSONObjectHelper.JSONToRecord(const AJSON: TJSONObject; - const AFilterProc: TToRecordFilterProc = nil): T; -begin - Assert(Assigned(AJSON)); - Result := AJSON.ToRecord(AFilterProc); -end; - -class function TJSONObjectHelper.ObjectToJSON(const AObject: TObject; - const AOptions: TJsonOptions): TJSONObject; -begin - Result := TJSON.ObjectToJsonObject(AObject, AOptions); -end; - -function TJSONObjectHelper.ReadArrayValue(const AName: string): TJSONArray; -begin - Result := nil; - TryGetValue(AName, Result); -end; - -function TJSONObjectHelper.ReadArrayValue(const AName: string): TArray; -var - LArray: TJSONArray; -begin - LArray := ReadArrayValue(AName); - if Assigned(LArray) then - Result := LArray.ToArrayOfRecord - else - Result := []; -end; - -function TJSONObjectHelper.ReadBoolValue(const AName: string; const ADefault: Boolean): Boolean; -{$ifdef Delphi10Seattle_UP} -var - LValue: TJSONBool; -begin - Result := ADefault; - if Assigned(Self) and TryGetValue(AName, LValue) then - Result := LValue is TJSONTrue; -end; -{$else} -var - LValue: TJSONValue; -begin - Result := ADefault; - if Assigned(Self) and TryGetValue(AName, LValue) then - Result := LValue is TJSONTrue; -end; -{$endif} - - -function TJSONObjectHelper.ReadDateTimeValue(const AName: string; const ADefault: TDateTime; - const AReturnUTC: Boolean): TDateTime; -begin - Result := ADefault; - if Assigned(Self) then - Result := JSONToDate(ReadStringValue(AName), AReturnUTC); -end; - -function TJSONObjectHelper.ReadDoubleValue(const AName: string; - const ADefault: Double): Double; -var - LValue: TJSONNumber; -begin - Result := ADefault; - if Assigned(Self) and TryGetValue(AName, LValue) then - Result := LValue.AsDouble; -end; - -procedure TJSONObjectHelper.FromRecord(const ARecord: TValue; const AFilterProc: TToJSONFilterProc = nil); - - function GetRecordFilterProc(const ARecordType: TRttiType): TToJSONFilterProc; - var - LMethod: TRttiMethod; - begin - Result := nil; - // looking for TMyRecord.ToJSONFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; - - LMethod := ARecordType.FindMethodFunc('ToJSONFilter'); - if Assigned(LMethod) then - Result := - procedure (const AField: TRttiField; const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean) - begin - AAccept := LMethod.Invoke(ARecord, [AField, AJSONObject]).AsBoolean; - end; - end; - -var - LType: TRttiType; - LField: TRttiField; - LFilterProc: TToJSONFilterProc; - LAccept: Boolean; - LValue: TValue; - LJSONName: string; -begin - LType := TRttiContext.Create.GetType(ARecord.TypeInfo); - - LFilterProc := AFilterProc; - if not Assigned(LFilterProc) then - LFilterProc := GetRecordFilterProc(LType); - - for LField in LType.GetFields do - begin - LAccept := True; - if Assigned(LFilterProc) then - LFilterProc(LField, ARecord, Self, LAccept); - - if LAccept then - begin - LJSONName := LField.Name; - LField.HasAttribute( - procedure (AAttr: JSONNameAttribute) - begin - LJSONName := AAttr.Name; - end - ); - if LJSONName <> '' then - begin - LValue := LField.GetValue(ARecord.GetReferenceToRawData); - - {$ifdef Delphi10Tokyo_UP} - if LValue.IsType(False) and (not LValue.IsArray) then - {$else} - if LValue.IsType and (not LValue.IsArray) then - {$endif} - WriteTValue(LJSONName, LValue.AsType) //unboxing TValue from TValue - else - WriteTValue(LJSONName, LValue); - end; - end; - end; -end; - -procedure TJSONObjectHelper.FromRecord(ARecord: T; const AFilterProc: TToJSONFilterProc = nil); -begin - FromRecord(TValue.From(ARecord), AFilterProc); -end; - -{$ifdef DelphiXE6_UP} -function TJSONObjectHelper.ReadInt64Value(const AName: string; - const ADefault: Int64): Int64; -var - LValue: TJSONNumber; -begin - Result := ADefault; - if Assigned(Self) and TryGetValue(AName, LValue) then - Result := LValue.AsInt64; -end; -{$endif} - -function TJSONObjectHelper.ReadIntegerValue(const AName: string; - const ADefault: Integer): Integer; -var - LValue: TJSONNumber; -begin - Result := ADefault; - if Assigned(Self) and TryGetValue(AName, LValue) then - Result := LValue.AsInt; -end; - -function TJSONObjectHelper.ReadStringValue(const AName, - ADefault: string): string; -var - LPair: TJSONPair; -begin - Result := ADefault; - if not Assigned(Self) then - Exit; - -{$ifdef DelphiXE6_UP} - LPair := GetPairByName(AName); -{$else} - LPair := Get(AName); -{$endif} - if Assigned(LPair) then - Result := LPair.JsonValue.Value; -end; - -function TJSONObjectHelper.ReadUnixTimeValue(const AName: string; - const ADefault: TDateTime): TDateTime; -var - LValue: Int64; -begin - Result := ADefault; -{$ifdef DelphiXE6_UP} - LValue := ReadInt64Value(AName); -{$else} - LValue := ReadIntegerValue(AName); -{$endif} - if LValue <> 0 then - Result := UnixToDateTime(LValue) -end; - -function TJSONObjectHelper.ReadValue(const AName: string; - const ADesiredType: TRttiType; const ANameCaseSensitive: Boolean; - out AValue: TValue): Boolean; -var - LValue: TJSONValue; - LName: string; -begin - LName := AName; - if not ANameCaseSensitive then - LName := GetExactPairName(LName); - - Result := TryGetValue(LName, LValue); - if Result then - AValue := TJSONValueToTValue(LValue, ADesiredType); -end; - -function TJSONObjectHelper.ReadValue(const AName: string; - const ADefault: TValue; const ADesiredType: TRttiType; - const ANameCaseSensitive: Boolean): TValue; -begin - Result := ADefault; - ReadValue(AName, ADesiredType, ANameCaseSensitive, Result); -end; - -class function TJSONObjectHelper.RecordToJSON(const ARecord: TValue; - const AFilterProc: TToJSONFilterProc): TJSONObject; -begin - Result := TJSONObject.Create; - try - Result.FromRecord(ARecord, AFilterProc); - except - Result.Free; - raise; - end; -end; - -class function TJSONObjectHelper.RecordToJSON(ARecord: T; - const AFilterProc: TToJSONFilterProc): TJSONObject; -begin - Result := TJSONObject.Create; - try - Result.FromRecord(ARecord, AFilterProc); - except - Result.Free; - raise; - end; -end; - -class function TJSONObjectHelper.TJSONValueToTValue( - const AValue: TJSONValue; const ADesiredType: TRttiType): TValue; -var - LArray: TValue; - LElementType: TRttiType; - LJSONArray: TJSONArray; - LJSONElement: TJSONValue; - LIndex: Integer; -begin -{$ifdef Delphi10Berlin_UP} - if AValue is TJSONBool then // Boolean - Result := TJSONBool(AValue).AsBoolean -{$else} - if (AValue is TJSONTrue) or (AValue is TJSONFalse) then - Result := AValue is TJSONTrue -{$endif} -// else if ADesiredType.Handle = TypeInfo(Variant) then -// Result := TValue. - else if AValue is TJSONNumber then // Numbers (Integer and Float) - begin -{$ifdef DelphiXE6_UP} - if ADesiredType.TypeKind in [tkInt64] then - Result := TJSONNumber(AValue).AsInt64 - else -{$endif} - if ADesiredType.TypeKind in [tkInteger] then - Result := TJSONNumber(AValue).AsInt - else - begin - if ADesiredType.Handle = TypeInfo(TValue) then - Result := GuessTValueFromString(AValue.ToString) - else - Result := TJSONNumber(AValue).AsDouble; - end; - - end - else if AValue is TJSONString then - begin - if ADesiredType is TRttiEnumerationType then // enumerated types - Result := TValue.FromOrdinal(ADesiredType.Handle, GetEnumValue(ADesiredType.Handle, TJSONString(AValue).Value)) - else if (ADesiredType.Handle = TypeInfo(TDateTime)) // dates - or (ADesiredType.Handle = TypeInfo(TDate)) - or (ADesiredType.Handle = TypeInfo(TTime)) - then - Result := JSONToDate(TJSONString(AValue).Value) - else - begin // strings - if (ADesiredType.Handle = TypeInfo(TValue)) or (ADesiredType.Handle = TypeInfo(Variant)) then - Result := GuessTValueFromString(TJSONString(AValue).Value) - else - Result := TJSONString(AValue).Value; - end; - end - else if AValue is TJSONNull then // null values - Result := TValue.Empty - else if AValue is TJSONObject then - Result := TJSONObject(AValue).ToRecord(ADesiredType) - else if (AValue is TJSONArray) then - begin - LJSONArray := TJSONArray(AValue); - if ADesiredType.IsArray(LElementType) then - begin - TValue.Make(nil, ADesiredType.Handle, LArray); - SetArrayLength(LArray, ADesiredType, LJSONArray.Count); - for LIndex := 0 to LJSONArray.Count-1 do - begin - LJSONElement := LJSONArray.Items[LIndex]; - LArray.SetArrayElement(LIndex, TJSONValueToTValue(LJSONElement, LElementType)); - end; - Result := LArray; - end; - end - else - raise Exception.CreateFmt('Unable to put JSON Value [%s] in TValue', [AValue.ClassName]); -end; - -function TJSONObjectHelper.ToRecord(const ARecordType: TRttiType; - const AFilterProc: TToRecordFilterProc = nil): TValue; -var - LField: TRttiField; - LValue: TValue; - LRecordInstance: Pointer; - LFilterProc: TToRecordFilterProc; - LAccept: Boolean; - LJSONName: string; - LAssignedValuesField: TRttiField; - LAssignedValues: TArray; - - function GetRecordFilterProc: TToRecordFilterProc; - var - LMethod: TRttiMethod; - begin - Result := nil; - // looking for TMyRecord.ToRecordFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; - - LMethod := ARecordType.FindMethodFunc('ToRecordFilter'); - if Assigned(LMethod) then - Result := - procedure (const AField: TRttiField; const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean) - begin - AAccept := LMethod.Invoke(ARecord, [AField, AJSONObject]).AsBoolean; - end; - end; - -begin - TValue.Make(nil, ARecordType.Handle, Result); - LRecordInstance := Result.GetReferenceToRawData; - - LFilterProc := AFilterProc; - if not Assigned(LFilterProc) then - LFilterProc := GetRecordFilterProc(); - - LAssignedValuesField := ARecordType.GetField('_AssignedValues'); - if Assigned(LAssignedValuesField) - and not LAssignedValuesField.FieldType.IsDynamicArrayOf - then - LAssignedValuesField := nil; - LAssignedValues := []; - - for LField in ARecordType.GetFields do - begin - LAccept := True; - if Assigned(LFilterProc) then - LFilterProc(LField, Result, Self, LAccept); - - if LAccept then - begin - LJSONName := LField.Name; - LField.HasAttribute( - procedure (AAttr: JSONNameAttribute) - begin - LJSONName := AAttr.Name; - end - ); - if LJSONName <> '' then - begin - if ReadValue(LJSONName, LField.FieldType, True, LValue) then - begin - LField.SetValue(LRecordInstance, LValue); - LAssignedValues := LAssignedValues + [LField.Name]; - end - else - LField.SetValue(LRecordInstance, TValue.Empty); - end; - end; - end; - if Assigned(LAssignedValuesField) then - LAssignedValuesField.SetValue(LRecordInstance, TValue.From>(LAssignedValues)); -end; - -function TJSONObjectHelper.ToRecord(const AFilterProc: TToRecordFilterProc = nil): T; -begin - Result := ToRecord(TRttiContext.Create.GetType(TypeInfo(T)), AFilterProc).AsType; -end; - -procedure TJSONObjectHelper.WriteArrayValue(const AName: string; - const AArray: TJSONArray); -begin - AddPair(AName, AArray); -end; - -procedure TJSONObjectHelper.WriteArrayValue(const AName: string; - const AArray: TArray); -begin - WriteArrayValue(AName, TJSONArray.ArrayOfRecordToJSON(AArray)); -end; - -procedure TJSONObjectHelper.WriteBoolValue(const AName: string; - const AValue: Boolean); -var - LDummy: TJSONValue; -begin - if TryGetValue(AName, LDummy) then - RemovePair(AName); - - AddPair(AName, BooleanToTJSON(AValue)); -end; - -procedure TJSONObjectHelper.WriteDateTimeValue(const AName: string; - const AValue: TDateTime; const AInputIsUTC: Boolean); -begin - WriteStringValue(AName, DateToJSON(AValue, AInputIsUTC)); -end; - -procedure TJSONObjectHelper.WriteDoubleValue(const AName: string; - const AValue: Double); -var - LDummy: TJSONValue; -begin - if TryGetValue(AName, LDummy) then - RemovePair(AName); - - AddPair(AName, TJSONNumber.Create(AValue)); -end; - -procedure TJSONObjectHelper.WriteInt64Value(const AName: string; - const AValue: Int64); -var - LDummy: TJSONValue; -begin - if TryGetValue(AName, LDummy) then - RemovePair(AName); - - AddPair(AName, TJSONNumber.Create(AValue)); -end; - -procedure TJSONObjectHelper.WriteIntegerValue(const AName: string; - const AValue: Integer); -var - LDummy: TJSONValue; -begin - if TryGetValue(AName, LDummy) then - RemovePair(AName); - - AddPair(AName, TJSONNumber.Create(AValue)); -end; - -procedure TJSONObjectHelper.WriteStringValue(const AName, AValue: string); -var - LDummy: TJSONValue; -begin - if TryGetValue(AName, LDummy) then - RemovePair(AName); - - if AValue <> '' then - AddPair(AName, TJSONString.Create(AValue)); -end; - -procedure TJSONObjectHelper.WriteTValue(const AName: string; - const AValue: TValue); -begin - AddPair(AName, TValueToJSONValue(AValue)); -end; - -procedure TJSONObjectHelper.WriteUnixTimeValue(const AName: string; - const AValue: TDateTime); -begin - WriteInt64Value(AName, DateTimeToUnix(AValue)); -end; - -{ JSONNameAttribute } - -constructor JSONNameAttribute.Create(const AName: string); -begin - inherited Create; - FName := AName; -end; - -end. \ No newline at end of file +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) +unit MARS.Core.JSON; + +{$I MARS.inc} + +interface + +uses +{$IFDEF Delphi10Rio_UP} + Generics.Collections, +{$ENDIF} +{$ifdef DelphiXE6_UP} + JSON +{$else} + DBXJSON +{$endif} + , SysUtils +{$ifdef DelphiXE2_UP} + , System.Rtti +{$else} + , Rtti +{$endif} + , TypInfo, REST.JSON +; + +type + TJSONAncestor = {$ifdef DelphiXE6_UP}JSON.TJSONAncestor{$else}DBXJSON.TJSONAncestor{$endif}; + TJSONPair = {$ifdef DelphiXE6_UP}JSON.TJSONPair{$else}DBXJSON.TJSONPair{$endif}; + TJSONValue = {$ifdef DelphiXE6_UP}JSON.TJSONValue{$else}DBXJSON.TJSONValue{$endif}; + TJSONTrue = {$ifdef DelphiXE6_UP}JSON.TJSONTrue{$else}DBXJSON.TJSONTrue{$endif}; + TJSONString = {$ifdef DelphiXE6_UP}JSON.TJSONString{$else}DBXJSON.TJSONString{$endif}; + TJSONNumber = {$ifdef DelphiXE6_UP}JSON.TJSONNumber{$else}DBXJSON.TJSONNumber{$endif}; + TJSONObject = {$ifdef DelphiXE6_UP}JSON.TJSONObject{$else}DBXJSON.TJSONObject{$endif}; + TJSONNull = {$ifdef DelphiXE6_UP}JSON.TJSONNull{$else}DBXJSON.TJSONNull{$endif}; + TJSONFalse = {$ifdef DelphiXE6_UP}JSON.TJSONFalse{$else}DBXJSON.TJSONFalse{$endif}; + TJSONArray = {$ifdef DelphiXE6_UP}JSON.TJSONArray{$else}DBXJSON.TJSONArray{$endif}; + + TJSONValueHelper = class helper for TJSONValue + public +{$ifndef DelphiXE7_UP} + function TryGetValue(const APath: string; out AValue: T): Boolean; overload; + function ToJSON: string; +{$endif} + end; + + JSONNameAttribute = class(TCustomAttribute) + private + FName: string; + public + constructor Create(const AName: string); + property Name: string read FName; + end; + + TToRecordFilterProc = reference to procedure (const AField: TRttiField; + const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean); + + TToJSONFilterProc = reference to procedure (const AField: TRttiField; + const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean); + + TJSONRawString = type string; + +{$ifndef DelphiXE6_UP} + TJSONArrayEnumerator = class + private + FIndex: Integer; + FArray: TJSONArray; + public + constructor Create(const AArray: TJSONArray); + function GetCurrent: TJSONValue; inline; + function MoveNext: Boolean; + property Current: TJSONValue read GetCurrent; + end; +{$endif} + + TJSONArrayHelper= class helper for TJSONArray + private + {$ifndef DelphiXE6_UP} + function GetCount: Integer; inline; + function GetValue(const Index: Integer): TJSONValue; inline; + {$endif} + public + function ToArrayOfRecord(): TArray; + procedure FromArrayOfRecord(const AArray: TArray; + const AFilterProc: TToJSONFilterProc = nil); + procedure FromArrayOfObject(const AArray: TArray; + const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]); + function ForEach(const AFunc: TFunc): Integer; + + {$ifndef DelphiXE6_UP} + function GetEnumerator: TJSONArrayEnumerator; + + property Count: Integer read GetCount; + property Items[const Index: Integer]: TJSONValue read GetValue; + {$endif} + + class function ArrayOfRecordToJSON(const AArray: TArray; const AFilterProc: TToJSONFilterProc = nil): TJSONArray; + class function ArrayOfObjectToJSON(const AArray: TArray): TJSONArray; + end; + + TJSONObjectHelper = class helper(TJSONValueHelper) for TJSONObject + private +{$ifndef DelphiXE6_UP} + function GetCount: Integer; inline; + function GetPair(const Index: Integer): TJSONPair; inline; +{$endif} + function GetExactPairName(const ACaseInsensitiveName: string): string; + public + function ReadStringValue(const AName: string; const ADefault: string = ''): string; + function ReadIntegerValue(const AName: string; const ADefault: Integer = 0): Integer; +{$ifdef DelphiXE6_UP} + function ReadInt64Value(const AName: string; const ADefault: Int64 = 0): Int64; +{$endif} + function ReadDoubleValue(const AName: string; const ADefault: Double = 0.0): Double; + function ReadBoolValue(const AName: string; const ADefault: Boolean = False): Boolean; + function ReadDateTimeValue(const AName: string; const ADefault: TDateTime = 0.0; + const AReturnUTC: Boolean = False): TDateTime; + function ReadUnixTimeValue(const AName: string; const ADefault: TDateTime = 0.0): TDateTime; + function ReadValue(const AName: string; const ADefault: TValue; + const ADesiredType: TRttiType; const ANameCaseSensitive: Boolean = True): TValue; overload; + function ReadValue(const AName: string; const ADesiredType: TRttiType; + const ANameCaseSensitive: Boolean; out AValue: TValue): Boolean; overload; + function ReadArrayValue(const AName: string): TJSONArray; overload; inline; + function ReadArrayValue(const AName: string): TArray; overload; inline; + + procedure WriteStringValue(const AName: string; const AValue: string); + procedure WriteIntegerValue(const AName: string; const AValue: Integer); + procedure WriteInt64Value(const AName: string; const AValue: Int64); + procedure WriteDoubleValue(const AName: string; const AValue: Double); + procedure WriteBoolValue(const AName: string; const AValue: Boolean); + procedure WriteDateTimeValue(const AName: string; const AValue: TDateTime; + const AInputIsUTC: Boolean = False); + procedure WriteUnixTimeValue(const AName: string; const AValue: TDateTime); + procedure WriteTValue(const AName: string; const AValue: TValue); + procedure WriteArrayValue(const AName: string; const AArray: TJSONArray); overload; inline; + procedure WriteArrayValue(const AName: string; const AArray: TArray); overload; inline; + + procedure FromRecord(ARecord: T; const AFilterProc: TToJSONFilterProc = nil); overload; + procedure FromRecord(const ARecord: TValue; const AFilterProc: TToJSONFilterProc = nil); overload; + function ToRecord(const AFilterProc: TToRecordFilterProc = nil): T; overload; + function ToRecord(const ARecordType: TRttiType; + const AFilterProc: TToRecordFilterProc = nil): TValue; overload; + +{$ifndef DelphiXE6_UP} + property Count: Integer read GetCount; + property Pairs[const Index: Integer]: TJSONPair read GetPair; +{$endif} + + class function ObjectToJSON(const AObject: TObject; + const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): TJSONObject; overload; + + class function JSONToObject(const AJSON: TJSONObject; + const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): T; overload; + + class function JSONToObject(const AClassType: TClass; const AJSON: TJSONObject; + const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): TObject; overload; + + class function RecordToJSON(ARecord: T; + const AFilterProc: TToJSONFilterProc = nil): TJSONObject; overload; + class function RecordToJSON(const ARecord: TValue; + const AFilterProc: TToJSONFilterProc = nil): TJSONObject; overload; + + class function JSONToRecord(const AJSON: TJSONObject; + const AFilterProc: TToRecordFilterProc = nil): T; overload; + class function JSONToRecord(const ARecordType: TRttiType; const AJSON: TJSONObject; + const AFilterProc: TToRecordFilterProc = nil): TValue; overload; + + class function TValueToJSONValue(const AValue: TValue): TJSONValue; + class function TJSONValueToTValue(const AValue: TJSONValue; const ADesiredType: TRttiType): TValue; + end; + + function StringArrayToJsonArray(const AStringArray: TArray): TJSONArray; + function JsonArrayToStringArray(const AJSONArray: TJSONArray): TArray; + +implementation + +uses + DateUtils, Variants, StrUtils + , MARS.Core.Utils + , MARS.Rtti.Utils +; + +class function TJSONObjectHelper.TValueToJSONValue( + const AValue: TValue): TJSONValue; +var + LArray: TJSONArray; + LIndex: Integer; +begin + if AValue.IsEmpty and not AValue.IsArray then + Result := TJSONNull.Create + + else if (AValue.Kind in [tkString, tkUString, tkChar, {$ifdef DelphiXE6_UP} tkWideChar, {$endif} tkLString, tkWString]) then + Result := TJSONString.Create(AValue.AsString) + + else if AValue.IsArray then + begin + LArray := TJSONArray.Create; + try + for LIndex := 0 to AValue.GetArrayLength-1 do + LArray.AddElement(TValueToJSONValue(AValue.GetArrayElement(LIndex))); + + Result := LArray; + except + LArray.Free; + raise; + end; + end + + else if (AValue.Kind in [tkRecord]) then + Result := TJSONObject.RecordToJSON(AValue) + + else if (AValue.IsType) then + Result := BooleanToTJSON(AValue.AsType) + + else if AValue.TypeInfo = TypeInfo(TDateTime) then + Result := TJSONString.Create( DateToJSON(AValue.AsType) ) + else if AValue.TypeInfo = TypeInfo(TDate) then + Result := TJSONString.Create( DateToJSON(AValue.AsType) ) + else if AValue.TypeInfo = TypeInfo(TTime) then + Result := TJSONString.Create( DateToJSON(AValue.AsType) ) + + else if (AValue.Kind in [tkInt64]) then + Result := TJSONNumber.Create( AValue.AsType ) + else if (AValue.Kind in [tkInteger]) then + Result := TJSONNumber.Create( AValue.AsType ) + + else if (AValue.Kind in [tkFloat]) then + Result := TJSONNumber.Create( AValue.AsType ) + + else if (AValue.Kind in [tkVariant]) then + Result := TValueToJSONValue( TValue.FromVariant(AValue.AsVariant) ) + + else if (AValue.IsInstanceOf(TObject)) then + Result := ObjectToJSON(AValue.AsObject) + + else + Result := TJSONString.Create(AValue.ToString); + +end; + +function StringArrayToJsonArray(const AStringArray: TArray): TJSONArray; +var + LIndex: Integer; +begin + Result := TJSONArray.Create; + try + for LIndex := Low(AStringArray) to High(AStringArray) do + Result.Add(AStringArray[LIndex]); + except + Result.Free; + raise; + end; +end; + +function JsonArrayToStringArray(const AJSONArray: TJSONArray): TArray; +var + LElement: TJSONValue; + LIndex: Integer; +begin + SetLength(Result, AJSONArray.Count); + + for LIndex := 0 to AJSONArray.Count-1 do + begin + LElement := AJSONArray.Items[LIndex]; + if LElement is TJSONString then + Result[LIndex] := TJSONString(LElement).Value + else if LElement is TJSONNumber then + Result[LIndex] := TJSONNumber(LElement).ToString + else if LElement is TJSONTrue then + Result[LIndex] := 'true' + else if LElement is TJSONFalse then + Result[LIndex] := 'false' + else + Result[LIndex] := LElement.ToString; + end; +end; + +{ TJSONValueHelper } +{$ifndef DelphiXE7_UP} +function TJSONValueHelper.TryGetValue(const APath: string; + out AValue: T): Boolean; +var + LJSONValue: TJSONValue; + LPair: TJSONPair; +begin + LJSONValue := nil; + if Self is TJSONObject then + begin + LPair := TJSONObject(Self).Get(APath); + if Assigned(LPair) then + LJSONValue := LPair.JsonValue; + end; + Result := LJSONValue <> nil; + if Result then + begin + try + AValue := T(LJSONValue); + except + Result := False; + end; + end; +end; +{$endif} + +{$ifndef DelphiXE7_UP} +function TJSONValueHelper.ToJSON: string; +var + LBytes: TBytes; +begin + SetLength(LBytes, Length(ToString) * 6); + SetLength(LBytes, ToBytes(LBytes, 0)); + Result := TEncoding.Default.GetString(LBytes); +end; +{$endif} + +{ TJSONArrayEnumerator } + +{$ifndef DelphiXE6_UP} +constructor TJSONArrayEnumerator.Create(const AArray: TJSONArray); +begin + inherited Create; + FIndex := -1; + FArray := AArray; +end; + +function TJSONArrayEnumerator.GetCurrent: TJSONValue; +begin + Result := FArray.GetValue(FIndex); +end; + +function TJSONArrayEnumerator.MoveNext: Boolean; +begin + Result := FIndex < FArray.Count - 1; + if Result then + Inc(FIndex); +end; +{$endif} + +{ TJSONArrayHelper } + +function TJSONArrayHelper.ToArrayOfRecord: TArray; +var + LElement: TJSONValue; +begin + Result := []; + for LElement in Self do + Result := Result + [(LElement as TJSONObject).ToRecord()] +end; + +class function TJSONArrayHelper.ArrayOfObjectToJSON( + const AArray: TArray): TJSONArray; +begin + Result := TJSONArray.Create; + try + Result.FromArrayOfObject(AArray); + except + Result.Free; + raise; + end; +end; + +class function TJSONArrayHelper.ArrayOfRecordToJSON(const AArray: TArray; + const AFilterProc: TToJSONFilterProc): TJSONArray; +begin + Result := TJSONArray.Create; + try + Result.FromArrayOfRecord(AArray, AFilterProc); + except + Result.Free; + raise; + end; +end; + +function TJSONArrayHelper.ForEach(const AFunc: TFunc): Integer; +var + LIndex: Integer; + LItem: TJSONValue; +begin + Result := 0; + if not Assigned(AFunc) then + Exit; + for LIndex := 0 to Count-1 do + begin + LItem := Items[Lindex]; + if LItem is T then + begin + if not AFunc(T(LItem)) then + Break; + Inc(Result); + end; + end; +end; + +procedure TJSONArrayHelper.FromArrayOfObject(const AArray: TArray; + const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]); +var + LObject: T; + LObj: TJSONObject; +begin + // clear all + while Count > 0 do + Remove(0); + + for LObject in AArray do + AddElement(TJSONObject.ObjectToJSON(LObject, AOptions)); +end; + +procedure TJSONArrayHelper.FromArrayOfRecord(const AArray: TArray; + const AFilterProc: TToJSONFilterProc); +var + LRecord: T; + LObj: TJSONObject; +begin + // clear all + while Count > 0 do + Remove(0); + + for LRecord in AArray do + begin + LObj := TJSONObject.Create; + try + LObj.FromRecord(LRecord, AFilterProc); + AddElement(LObj); + except + LObj.Free; + raise; + end; + end; +end; + + +{$ifndef DelphiXE6_UP} + +function TJSONArrayHelper.GetCount: Integer; +begin + Result := Size; +end; + +function TJSONArrayHelper.GetEnumerator: TJSONArrayEnumerator; +begin + Result := TJSONArrayEnumerator.Create(Self); +end; + +function TJSONArrayHelper.GetValue(const Index: Integer): TJSONValue; +begin + Result := Get(Index); +end; +{$endif} + +{ TJSONObjectHelper } + +{$ifndef DelphiXE6_UP} +function TJSONObjectHelper.GetCount: Integer; +begin + Result := Size; +end; + +function TJSONObjectHelper.GetPair(const Index: Integer): TJSONPair; +begin + Result := Get(Index); +end; +{$endif} + +function TJSONObjectHelper.GetExactPairName( + const ACaseInsensitiveName: string): string; +var + LIndex: Integer; + LPair: TJSONPair; +begin + Result := ACaseInsensitiveName; + for LIndex := 0 to Count -1 do + begin + LPair := Pairs[LIndex]; + if SameText(LPair.JsonString.Value, ACaseInsensitiveName) then + begin + Result := LPair.JsonString.Value; + Exit; + end; + end; +end; + + +class function TJSONObjectHelper.JSONToObject(const AClassType: TClass; + const AJSON: TJSONObject; const AOptions: TJsonOptions): TObject; +var + LConstructor: TRttiMethod; +begin + Result := nil; + + LConstructor := TRTTIHelper.FindParameterLessConstructor(AClassType); + if not Assigned(LConstructor) then + Exit; + + Result := LConstructor.Invoke(AClassType, []).AsObject; + try + TJson.JsonToObject(Result, AJSON, AOptions); + except + Result.Free; + raise; + end; +end; + +class function TJSONObjectHelper.JSONToObject(const AJSON: TJSONObject; + const AOptions: TJsonOptions = [joDateIsUTC, joDateFormatISO8601]): T; +begin + Result := TJSON.JsonToObject(AJSON, AOptions); +end; + +class function TJSONObjectHelper.JSONToRecord(const ARecordType: TRttiType; + const AJSON: TJSONObject; const AFilterProc: TToRecordFilterProc): TValue; +begin + Assert(Assigned(AJSON)); + Result := AJSON.ToRecord(ARecordType, AFilterProc); +end; + +class function TJSONObjectHelper.JSONToRecord(const AJSON: TJSONObject; + const AFilterProc: TToRecordFilterProc = nil): T; +begin + Assert(Assigned(AJSON)); + Result := AJSON.ToRecord(AFilterProc); +end; + +class function TJSONObjectHelper.ObjectToJSON(const AObject: TObject; + const AOptions: TJsonOptions): TJSONObject; +begin + Result := TJSON.ObjectToJsonObject(AObject, AOptions); +end; + +function TJSONObjectHelper.ReadArrayValue(const AName: string): TJSONArray; +begin + Result := nil; + TryGetValue(AName, Result); +end; + +function TJSONObjectHelper.ReadArrayValue(const AName: string): TArray; +var + LArray: TJSONArray; +begin + LArray := ReadArrayValue(AName); + if Assigned(LArray) then + Result := LArray.ToArrayOfRecord + else + Result := []; +end; + +function TJSONObjectHelper.ReadBoolValue(const AName: string; const ADefault: Boolean): Boolean; +{$ifdef Delphi10Seattle_UP} +var + LValue: TJSONBool; +begin + Result := ADefault; + if Assigned(Self) and TryGetValue(AName, LValue) then + Result := LValue is TJSONTrue; +end; +{$else} +var + LValue: TJSONValue; +begin + Result := ADefault; + if Assigned(Self) and TryGetValue(AName, LValue) then + Result := LValue is TJSONTrue; +end; +{$endif} + + +function TJSONObjectHelper.ReadDateTimeValue(const AName: string; const ADefault: TDateTime; + const AReturnUTC: Boolean): TDateTime; +begin + Result := ADefault; + if Assigned(Self) then + Result := JSONToDate(ReadStringValue(AName), AReturnUTC, ADefault); +end; + +function TJSONObjectHelper.ReadDoubleValue(const AName: string; + const ADefault: Double): Double; +var + LValue: TJSONNumber; +begin + Result := ADefault; + if Assigned(Self) and TryGetValue(AName, LValue) then + Result := LValue.AsDouble; +end; + +procedure TJSONObjectHelper.FromRecord(const ARecord: TValue; const AFilterProc: TToJSONFilterProc = nil); + + function GetRecordFilterProc(const ARecordType: TRttiType): TToJSONFilterProc; + var + LMethod: TRttiMethod; + begin + Result := nil; + // looking for TMyRecord.ToJSONFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; + + LMethod := ARecordType.FindMethodFunc('ToJSONFilter'); + if Assigned(LMethod) then + Result := + procedure (const AField: TRttiField; const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean) + begin + AAccept := LMethod.Invoke(ARecord, [AField, AJSONObject]).AsBoolean; + end; + end; + +var + LType: TRttiType; + LField: TRttiField; + LFilterProc: TToJSONFilterProc; + LAccept: Boolean; + LValue: TValue; + LJSONName: string; +begin + LType := TRttiContext.Create.GetType(ARecord.TypeInfo); + + LFilterProc := AFilterProc; + if not Assigned(LFilterProc) then + LFilterProc := GetRecordFilterProc(LType); + + for LField in LType.GetFields do + begin + LAccept := True; + if Assigned(LFilterProc) then + LFilterProc(LField, ARecord, Self, LAccept); + + if LAccept then + begin + LJSONName := LField.Name; + LField.HasAttribute( + procedure (AAttr: JSONNameAttribute) + begin + LJSONName := AAttr.Name; + end + ); + if LJSONName <> '' then + begin + LValue := LField.GetValue(ARecord.GetReferenceToRawData); + + {$ifdef Delphi10Tokyo_UP} + if LValue.IsType(False) and (not LValue.IsArray) then + {$else} + if LValue.IsType and (not LValue.IsArray) then + {$endif} + WriteTValue(LJSONName, LValue.AsType) //unboxing TValue from TValue + else + WriteTValue(LJSONName, LValue); + end; + end; + end; +end; + +procedure TJSONObjectHelper.FromRecord(ARecord: T; const AFilterProc: TToJSONFilterProc = nil); +begin + FromRecord(TValue.From(ARecord), AFilterProc); +end; + +{$ifdef DelphiXE6_UP} +function TJSONObjectHelper.ReadInt64Value(const AName: string; + const ADefault: Int64): Int64; +var + LValue: TJSONNumber; +begin + Result := ADefault; + if Assigned(Self) and TryGetValue(AName, LValue) then + Result := LValue.AsInt64; +end; +{$endif} + +function TJSONObjectHelper.ReadIntegerValue(const AName: string; + const ADefault: Integer): Integer; +var + LValue: TJSONNumber; +begin + Result := ADefault; + if Assigned(Self) and TryGetValue(AName, LValue) then + Result := LValue.AsInt; +end; + +function TJSONObjectHelper.ReadStringValue(const AName, + ADefault: string): string; +var + LPair: TJSONPair; +begin + Result := ADefault; + if not Assigned(Self) then + Exit; + +{$ifdef DelphiXE6_UP} + LPair := GetPairByName(AName); +{$else} + LPair := Get(AName); +{$endif} + if Assigned(LPair) then + Result := LPair.JsonValue.Value; +end; + +function TJSONObjectHelper.ReadUnixTimeValue(const AName: string; + const ADefault: TDateTime): TDateTime; +var + LValue: Int64; +begin + Result := ADefault; +{$ifdef DelphiXE6_UP} + LValue := ReadInt64Value(AName); +{$else} + LValue := ReadIntegerValue(AName); +{$endif} + if LValue <> 0 then + Result := UnixToDateTime(LValue) +end; + +function TJSONObjectHelper.ReadValue(const AName: string; + const ADesiredType: TRttiType; const ANameCaseSensitive: Boolean; + out AValue: TValue): Boolean; +var + LValue: TJSONValue; + LName: string; +begin + LName := AName; + if not ANameCaseSensitive then + LName := GetExactPairName(LName); + + Result := TryGetValue(LName, LValue); + if Result then + AValue := TJSONValueToTValue(LValue, ADesiredType); +end; + +function TJSONObjectHelper.ReadValue(const AName: string; + const ADefault: TValue; const ADesiredType: TRttiType; + const ANameCaseSensitive: Boolean): TValue; +begin + Result := ADefault; + ReadValue(AName, ADesiredType, ANameCaseSensitive, Result); +end; + +class function TJSONObjectHelper.RecordToJSON(const ARecord: TValue; + const AFilterProc: TToJSONFilterProc): TJSONObject; +begin + Result := TJSONObject.Create; + try + Result.FromRecord(ARecord, AFilterProc); + except + Result.Free; + raise; + end; +end; + +class function TJSONObjectHelper.RecordToJSON(ARecord: T; + const AFilterProc: TToJSONFilterProc): TJSONObject; +begin + Result := TJSONObject.Create; + try + Result.FromRecord(ARecord, AFilterProc); + except + Result.Free; + raise; + end; +end; + +class function TJSONObjectHelper.TJSONValueToTValue( + const AValue: TJSONValue; const ADesiredType: TRttiType): TValue; +var + LArray: TValue; + LElementType: TRttiType; + LJSONArray: TJSONArray; + LJSONElement: TJSONValue; + LIndex: Integer; + LNewLength: NativeInt; +begin +{$ifdef Delphi10Berlin_UP} + if AValue is TJSONBool then // Boolean + Result := TJSONBool(AValue).AsBoolean +{$else} + if (AValue is TJSONTrue) or (AValue is TJSONFalse) then + Result := AValue is TJSONTrue +{$endif} +// else if ADesiredType.Handle = TypeInfo(Variant) then +// Result := TValue. + else if AValue is TJSONNumber then // Numbers (Integer and Float) + begin +{$ifdef DelphiXE6_UP} + if ADesiredType.TypeKind in [tkInt64] then + Result := TJSONNumber(AValue).AsInt64 + else +{$endif} + if ADesiredType.TypeKind in [tkInteger] then + Result := TJSONNumber(AValue).AsInt + else + begin + if ADesiredType.Handle = TypeInfo(TValue) then + Result := GuessTValueFromString(AValue.ToString) + else + Result := TJSONNumber(AValue).AsDouble; + end; + + end + else if AValue is TJSONString then + begin + if ADesiredType is TRttiEnumerationType then // enumerated types + Result := TValue.FromOrdinal(ADesiredType.Handle, GetEnumValue(ADesiredType.Handle, TJSONString(AValue).Value)) + else if (ADesiredType.Handle = TypeInfo(TDateTime)) // dates + or (ADesiredType.Handle = TypeInfo(TDate)) + or (ADesiredType.Handle = TypeInfo(TTime)) + then + Result := JSONToDate(TJSONString(AValue).Value) + else + begin // strings + if (ADesiredType.Handle = TypeInfo(TValue)) or (ADesiredType.Handle = TypeInfo(Variant)) then + Result := GuessTValueFromString(TJSONString(AValue).Value) + else + Result := TJSONString(AValue).Value; + end; + end + else if AValue is TJSONNull then // null values + Result := TValue.Empty + else if AValue is TJSONObject then + Result := TJSONObject(AValue).ToRecord(ADesiredType) + else if (AValue is TJSONArray) then + begin + LJSONArray := TJSONArray(AValue); + if ADesiredType.IsArray(LElementType) then + begin + TValue.Make(nil, ADesiredType.Handle, LArray); + LNewLength := LJSONArray.Count; + SetArrayLength(LArray, ADesiredType, @LNewLength); + //------------------------ + for LIndex := 0 to LJSONArray.Count-1 do + begin + LJSONElement := LJSONArray.Items[LIndex]; + LArray.SetArrayElement(LIndex, TJSONValueToTValue(LJSONElement, LElementType)); + end; + Result := LArray; + end; + end + else + raise Exception.CreateFmt('Unable to put JSON Value [%s] in TValue', [AValue.ClassName]); +end; + +function TJSONObjectHelper.ToRecord(const ARecordType: TRttiType; + const AFilterProc: TToRecordFilterProc = nil): TValue; +var + LField: TRttiField; + LValue: TValue; + LRecordInstance: Pointer; + LFilterProc: TToRecordFilterProc; + LAccept: Boolean; + LJSONName: string; + LAssignedValuesField: TRttiField; + LAssignedValues: TArray; + + function GetRecordFilterProc: TToRecordFilterProc; + var + LMethod: TRttiMethod; + begin + Result := nil; + // looking for TMyRecord.ToRecordFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; + + LMethod := ARecordType.FindMethodFunc('ToRecordFilter'); + if Assigned(LMethod) then + Result := + procedure (const AField: TRttiField; const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean) + begin + AAccept := LMethod.Invoke(ARecord, [AField, AJSONObject]).AsBoolean; + end; + end; + +begin + TValue.Make(nil, ARecordType.Handle, Result); + LRecordInstance := Result.GetReferenceToRawData; + + LFilterProc := AFilterProc; + if not Assigned(LFilterProc) then + LFilterProc := GetRecordFilterProc(); + + LAssignedValuesField := ARecordType.GetField('_AssignedValues'); + if Assigned(LAssignedValuesField) + and not LAssignedValuesField.FieldType.IsDynamicArrayOf + then + LAssignedValuesField := nil; + LAssignedValues := []; + + for LField in ARecordType.GetFields do + begin + LAccept := True; + if Assigned(LFilterProc) then + LFilterProc(LField, Result, Self, LAccept); + + if LAccept then + begin + LJSONName := LField.Name; + LField.HasAttribute( + procedure (AAttr: JSONNameAttribute) + begin + LJSONName := AAttr.Name; + end + ); + if LJSONName <> '' then + begin + if ReadValue(LJSONName, LField.FieldType, True, LValue) then + begin + LField.SetValue(LRecordInstance, LValue); + LAssignedValues := LAssignedValues + [LField.Name]; + end + else + LField.SetValue(LRecordInstance, TValue.Empty); + end; + end; + end; + if Assigned(LAssignedValuesField) then + LAssignedValuesField.SetValue(LRecordInstance, TValue.From>(LAssignedValues)); +end; + +function TJSONObjectHelper.ToRecord(const AFilterProc: TToRecordFilterProc = nil): T; +begin + Result := ToRecord(TRttiContext.Create.GetType(TypeInfo(T)), AFilterProc).AsType; +end; + +procedure TJSONObjectHelper.WriteArrayValue(const AName: string; + const AArray: TJSONArray); +begin + AddPair(AName, AArray); +end; + +procedure TJSONObjectHelper.WriteArrayValue(const AName: string; + const AArray: TArray); +begin + WriteArrayValue(AName, TJSONArray.ArrayOfRecordToJSON(AArray)); +end; + +procedure TJSONObjectHelper.WriteBoolValue(const AName: string; + const AValue: Boolean); +var + LDummy: TJSONValue; +begin + if TryGetValue(AName, LDummy) then + RemovePair(AName); + + AddPair(AName, BooleanToTJSON(AValue)); +end; + +procedure TJSONObjectHelper.WriteDateTimeValue(const AName: string; + const AValue: TDateTime; const AInputIsUTC: Boolean); +begin + WriteStringValue(AName, DateToJSON(AValue, AInputIsUTC)); +end; + +procedure TJSONObjectHelper.WriteDoubleValue(const AName: string; + const AValue: Double); +var + LDummy: TJSONValue; +begin + if TryGetValue(AName, LDummy) then + RemovePair(AName); + + AddPair(AName, TJSONNumber.Create(AValue)); +end; + +procedure TJSONObjectHelper.WriteInt64Value(const AName: string; + const AValue: Int64); +var + LDummy: TJSONValue; +begin + if TryGetValue(AName, LDummy) then + RemovePair(AName); + + AddPair(AName, TJSONNumber.Create(AValue)); +end; + +procedure TJSONObjectHelper.WriteIntegerValue(const AName: string; + const AValue: Integer); +var + LDummy: TJSONValue; +begin + if TryGetValue(AName, LDummy) then + RemovePair(AName); + + AddPair(AName, TJSONNumber.Create(AValue)); +end; + +procedure TJSONObjectHelper.WriteStringValue(const AName, AValue: string); +var + LDummy: TJSONValue; +begin + if TryGetValue(AName, LDummy) then + RemovePair(AName); + + if AValue <> '' then + AddPair(AName, TJSONString.Create(AValue)); +end; + +procedure TJSONObjectHelper.WriteTValue(const AName: string; + const AValue: TValue); +begin + AddPair(AName, TValueToJSONValue(AValue)); +end; + +procedure TJSONObjectHelper.WriteUnixTimeValue(const AName: string; + const AValue: TDateTime); +begin + WriteInt64Value(AName, DateTimeToUnix(AValue)); +end; + +{ JSONNameAttribute } + +constructor JSONNameAttribute.Create(const AName: string); +begin + inherited Create; + FName := AName; +end; + +end. diff --git a/Source/MARS.Core.MediaType.pas b/Source/MARS.Core.MediaType.pas index 233b037c..45bec2a4 100644 --- a/Source/MARS.Core.MediaType.pas +++ b/Source/MARS.Core.MediaType.pas @@ -70,6 +70,7 @@ TMediaType = class const TEXT_XML = 'text/xml'; const TEXT_HTML = 'text/html'; const APPLICATION_XML = 'application/xml'; + const APPLICATION_XML_FireDAC = 'application/xml-firedac'; const APPLICATION_JSON = 'application/json'; const APPLICATION_JSON_FireDAC = 'application/json-firedac'; const APPLICATION_XHTML_XML = 'application/xhtml+xml'; @@ -390,7 +391,7 @@ function TMediaTypeList.GetQualityFactor(const AMediaType: string): Double; begin Result := 0.0; for LItem in Self do - if LItem.ToString = AMediaType then + if (LItem.ToString = AMediaType) or (LItem.IsWildcard) then begin Result := LItem.QFactor; Break; @@ -402,14 +403,12 @@ class function TMediaTypeList.Intersect(const AList1: TArray; var LMediaType: string; begin - SetLength(Result, 0); + Result := []; + for LMediaType in AList1 do begin - if AList2.Contains(LMediaType) then - begin - SetLength(Result, Length(Result) + 1); - Result[Length(Result) -1 ] := LMediaType; - end; + if (LMediaType = TMediaType.WILDCARD) or AList2.Contains(LMediaType) then + Result := Result + [LMediaType]; end; end; diff --git a/Source/MARS.Core.MessageBodyReader.pas b/Source/MARS.Core.MessageBodyReader.pas index 6f70e9a3..d6c2eff9 100644 --- a/Source/MARS.Core.MessageBodyReader.pas +++ b/Source/MARS.Core.MessageBodyReader.pas @@ -22,9 +22,7 @@ interface ; type - IMessageBodyReader = interface - ['{C22068E1-3085-482D-9EAB-4829C7AE87C0}'] - + IMessageBodyReader = interface ['{C22068E1-3085-482D-9EAB-4829C7AE87C0}'] function ReadFrom( {$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} const ADestination: TRttiObject; const AMediaType: TMediaType; @@ -90,6 +88,19 @@ TMARSMessageBodyReaderRegistry = class const AFFINITY_ZERO = 0; end; + TMARSMessageBodyReader = class + private + protected + public + class function ReadWith( + {$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation): TValue; inline; + class function GetDesiredEncoding(const AActivation: IMARSActivation; + var AEncoding: TEncoding): Boolean; + end; + + implementation uses @@ -364,4 +375,58 @@ procedure TMARSMessageBodyReaderRegistry.RegisterReader( FRegistry.Add(LEntryInfo) end; +{ TMARSMessageBodyReader } + +class function TMARSMessageBodyReader.GetDesiredEncoding( + const AActivation: IMARSActivation; var AEncoding: TEncoding): Boolean; +var + LEncoding: TEncoding; + LFound: Boolean; +begin + if not Assigned(AActivation) then + begin + AEncoding := TEncoding.UTF8; + Result := False; + Exit; + end; + + LFound := False; + // look for attribute on Method + if Assigned(AActivation.Method) and not AActivation.Method.HasAttribute( + procedure(AAttr: EncodingAttribute) + begin + LEncoding := AAttr.Encoding; + LFound := True; + end + ) then // if not found, fallback looking for attribute on Resource + begin + if Assigned(AActivation.Resource) then + AActivation.Resource.HasAttribute( + procedure(AAttr: EncodingAttribute) + begin + LEncoding := AAttr.Encoding; + LFound := True; + end + ); + end; + + Result := False; + if LFound then + begin + AEncoding := LEncoding; + Result := True; + end; +end; + +class function TMARSMessageBodyReader.ReadWith( + {$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation): TValue; +var + LMBReader: IMessageBodyReader; +begin + LMBReader := T.Create; + Result := LMBReader.ReadFrom(AInputData, ADestination, AMediaType, AActivation); +end; + end. diff --git a/Source/MARS.Core.MessageBodyReaders.pas b/Source/MARS.Core.MessageBodyReaders.pas index d35c27b2..673beee9 100644 --- a/Source/MARS.Core.MessageBodyReaders.pas +++ b/Source/MARS.Core.MessageBodyReaders.pas @@ -10,14 +10,12 @@ interface uses - Classes, SysUtils, Rtti + Classes, SysUtils, System.Rtti, System.TypInfo - , MARS.Core.Attributes - , MARS.Core.Activation.Interfaces - , MARS.Core.Declarations - , MARS.Core.MediaType - , MARS.Core.MessageBodyReader - ; +, MARS.Core.Attributes, MARS.Core.Activation.Interfaces, MARS.Core.Declarations +, MARS.Core.MediaType, MARS.Core.MessageBodyReader +, MARS.Core.RequestAndResponse.Interfaces +; type [Consumes(TMediaType.APPLICATION_JSON)] @@ -58,6 +56,22 @@ TJSONValueReader = class(TInterfacedObject, IMessageBodyReader) end; + [Consumes(TMediaType.APPLICATION_XML)] + TXMLReader = class(TInterfacedObject, IMessageBodyReader) + public + function ReadFrom( + {$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation + ): TValue; virtual; + + class function ReadXML( + {$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation + ): TValue; + end; + [Consumes(TMediaType.APPLICATION_JSON) , Consumes(TMediaType.APPLICATION_FORM_URLENCODED_TYPE) , Consumes(TMediaType.MULTIPART_FORM_DATA) @@ -129,11 +143,11 @@ TArrayOfTFormParamReader = class(TInterfacedObject, IMessageBodyReader) implementation uses - StrUtils, NetEncoding, Web.HttpApp - , MARS.Core.JSON - , MARS.Core.Utils, MARS.Rtti.Utils - {$ifdef DelphiXE7_UP}, System.JSON {$endif} - ; + StrUtils, NetEncoding, Generics.Collections +{$ifdef DelphiXE7_UP}, System.JSON {$endif} +, Xml.XMLIntf, XMLDoc +, MARS.Core.JSON, MARS.Core.Utils, MARS.Rtti.Utils +; { TJSONValueReader } @@ -205,7 +219,7 @@ function TRecordReader.ReadFrom( ): TValue; var LJSON: TJSONObject; - LRequest: TWebRequest; + LRequest: IMARSRequest; begin Result := TValue.Empty; @@ -214,7 +228,7 @@ function TRecordReader.ReadFrom( then begin LRequest := AActivation.Request; - Result := StringsToRecord(LRequest.ContentFields, ADestination.GetRttiType + Result := StringsToRecord(LRequest.GetFormParams, ADestination.GetRttiType , procedure (const AName: string; const AField: TRttiField; var AValue: TValue) begin if AField.FieldType.Handle = TypeInfo(TFormParamFile) then @@ -277,6 +291,7 @@ function TArrayOfObjectReader.ReadFrom( LArray: TValue; LArrayType: TRttiType; LIndex: Integer; + LNewLength: NativeInt; begin Result := TValue.Empty; LArrayType := ADestination.GetRttiType; @@ -295,8 +310,9 @@ function TArrayOfObjectReader.ReadFrom( if LJSONValue is TJSONArray then begin LJSONArray := TJSONArray(LJSONValue); - - SetArrayLength(LArray, LArrayType, LJSONArray.Count); + LNewLength := LJSONArray.Count; + SetArrayLength(LArray, LArrayType, @LNewLength); + //------------------------ for LIndex := 0 to LJSONArray.Count-1 do //AM Refactor using ForEach begin LJSONObject := LJSONArray.Items[LIndex] as TJSONObject; @@ -312,7 +328,9 @@ function TArrayOfObjectReader.ReadFrom( end else if LJSONValue is TJSONObject then // a single obj, let's build an array of one element begin - SetArrayLength(LArray, LArrayType, 1); + LNewLength := 1; + SetArrayLength(LArray, LArrayType, @LNewLength); + //------------------------ LArray.SetArrayElement( 0 , TJSONObject.JSONToObject( @@ -344,6 +362,7 @@ function TArrayOfRecordReader.ReadFrom( LArray: TValue; LArrayType: TRttiType; LIndex: Integer; + LNewLength: NativeInt; begin Result := TValue.Empty; LArrayType := ADestination.GetRttiType; @@ -359,8 +378,9 @@ function TArrayOfRecordReader.ReadFrom( if LJSONValue is TJSONArray then begin LJSONArray := TJSONArray(LJSONValue); - - SetArrayLength(LArray, LArrayType, LJSONArray.Count); + LNewLength := LJSONArray.Count; + SetArrayLength(LArray, LArrayType, @LNewLength); + //------------------------ for LIndex := 0 to LJSONArray.Count-1 do //AM Refactor using ForEach begin LJSONObject := LJSONArray.Items[LIndex] as TJSONObject; @@ -370,7 +390,9 @@ function TArrayOfRecordReader.ReadFrom( end else if LJSONValue is TJSONObject then // a single obj, let's build an array of one element begin - SetArrayLength(LArray, LArrayType, 1); + LNewLength := 1; + SetArrayLength(LArray, LArrayType, @LNewLength); + //------------------------ LArray.SetArrayElement(0, TJSONObject(LJSONValue).ToRecord(LElementType)); end; @@ -388,42 +410,24 @@ function TStringReader.ReadFrom( const AActivation: IMARSActivation): TValue; var LType: TRttiType; - LSL: TStringList; - {$ifdef Delphi10Berlin_UP} - LBytesStream: TBytesStream; - {$endif} + LEncoding: TEncoding; + LText: string; begin Result := TValue.Empty; LType := ADestination.GetRttiType; {$ifdef Delphi10Berlin_UP} - LBytesStream := TBytesStream.Create(AInputData); - try - LSL := TStringList.Create; - try - LSL.LoadFromStream(LBytesStream); - if LType.IsDynamicArrayOf then - Result := TValue.From>( LSL.ToStringArray ) - else if LType.Handle = TypeInfo(string) then - Result := LSL.Text; - finally - LSL.Free; - end; - finally - LBytesStream.Free; - end; + if not TMARSMessageBodyReader.GetDesiredEncoding(AActivation, LEncoding) then + LEncoding := TEncoding.UTF8; // UTF8 by default + LText := LEncoding.GetString(AInputData); {$else} - LSL := TStringList.Create; - try - LSL.Text := string(AInputData); - if LType.IsDynamicArrayOf then - Result := TValue.From>( LSL.ToStringArray ) - else if LType.Handle = TypeInfo(string) then - Result := LSL.Text; - finally - LSL.Free; - end; + LText := string(AInputData); {$endif} + + if LType.IsDynamicArrayOf then + Result := TValue.From>( LText.Split([sLineBreak]) ) + else if LType.Handle = TypeInfo(string) then + Result := LText; end; { TFormParamReader } @@ -465,7 +469,7 @@ function TArrayOfTFormParamReader.ReadFrom( ): TValue; var LResult: TArray; - LRequest: TWebRequest; + LRequest: IMARSRequest; LIndex: Integer; begin LResult := []; @@ -476,10 +480,10 @@ function TArrayOfTFormParamReader.ReadFrom( begin LRequest := AActivation.Request; - for LIndex := 0 to LRequest.ContentFields.Count - 1 do - LResult := LResult + [TFormParam.CreateFromRequest(LRequest, LRequest.ContentFields.Names[LIndex])]; + for LIndex := 0 to LRequest.GetFormParamCount - 1 do + LResult := LResult + [TFormParam.CreateFromRequest(LRequest, LRequest.GetFormParamName(LIndex))]; - for LIndex := 0 to LRequest.Files.Count - 1 do + for LIndex := 0 to LRequest.GetFilesCount - 1 do LResult := LResult + [TFormParam.CreateFromRequest(LRequest, LIndex)]; end; @@ -487,6 +491,45 @@ function TArrayOfTFormParamReader.ReadFrom( end; +{ TXMLReader } + +function TXMLReader.ReadFrom( +{$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation): TValue; +var + LXMLDoc: IXMLDocument; + LEncoding: TEncoding; +begin + Result := TValue.Empty; + + LEncoding := TEncoding.UTF8; + + LXMLDoc := TXMLDocument.Create(nil); +{$ifdef Delphi10Berlin_UP} + LXMLDoc.LoadFromXML(LEncoding.GetString(AInputData)); +{$else} + LXMLDoc.LoadFromXML(AInputData); +{$endif} + Result := TValue.From(LXMLDoc); +end; + +class function TXMLReader.ReadXML( +{$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation): TValue; +var + LXMLReader: TXMLReader; +begin + LXMLReader := TXMLReader.Create; + try + Result := LXMLReader.ReadFrom(AInputData, ADestination, AMediaType, AActivation); + finally + LXMLReader.Free; + end; +end; + + procedure RegisterReaders; begin TMARSMessageBodyReaderRegistry.Instance.RegisterReader( @@ -514,6 +557,7 @@ procedure RegisterReaders; ); TMARSMessageBodyReaderRegistry.Instance.RegisterReader(TJSONValueReader); + TMARSMessageBodyReaderRegistry.Instance.RegisterReader(TXMLReader); TMARSMessageBodyReaderRegistry.Instance.RegisterReader(TStreamReader); TMARSMessageBodyReaderRegistry.Instance.RegisterReader( diff --git a/Source/MARS.Core.MessageBodyWriter.pas b/Source/MARS.Core.MessageBodyWriter.pas index d9c3635a..9c47b83c 100644 --- a/Source/MARS.Core.MessageBodyWriter.pas +++ b/Source/MARS.Core.MessageBodyWriter.pas @@ -61,7 +61,11 @@ TMARSMessageBodyRegistry = class const AGetAffinity: TGetAffinityFunction = nil); overload; procedure FindWriter(const AActivation: IMARSActivation; - out AWriter: IMessageBodyWriter; out AMediaType: TMediaType); + out AWriter: IMessageBodyWriter; out AMediaType: TMediaType); overload; + procedure FindWriter(const AActivation: IMARSActivation; + const AAccept: string; + out AWriter: IMessageBodyWriter; out AMediaType: TMediaType); overload; + procedure Enumerate(const AProc: TProc); @@ -76,8 +80,16 @@ TMARSMessageBodyRegistry = class const AFFINITY_ZERO = 0; end; -function GetDesiredEncoding(const AActivation: IMARSActivation; var AEncoding: TEncoding): Boolean; -function GetEncodingName(const AEncoding: TEncoding): string; + TMARSMessageBodyWriter = class + private + protected + public + class procedure WriteWith( + const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); inline; + class function GetDesiredEncoding(const AActivation: IMARSActivation; + var AEncoding: TEncoding): Boolean; + end; implementation @@ -85,7 +97,7 @@ implementation MARS.Core.Utils, MARS.Rtti.Utils, MARS.Core.Exceptions, MARS.Core.Attributes ; -function GetDesiredEncoding(const AActivation: IMARSActivation; var AEncoding: TEncoding): Boolean; +class function TMARSMessageBodyWriter.GetDesiredEncoding(const AActivation: IMARSActivation; var AEncoding: TEncoding): Boolean; var LEncoding: TEncoding; LFound: Boolean; @@ -125,19 +137,19 @@ function GetDesiredEncoding(const AActivation: IMARSActivation; var AEncoding: T end; end; -function GetEncodingName(const AEncoding: TEncoding): string; +{ TMARSMessageBodyWriter } + +class procedure TMARSMessageBodyWriter.WriteWith(const AValue: TValue; + const AMediaType: TMediaType; AOutputStream: TStream; + const AActivation: IMARSActivation); +var + LMBWriter: IMessageBodyWriter; begin - Result := ''; - - if AEncoding = TEncoding.ANSI then Result := 'ANSI' - else if AEncoding = TEncoding.ASCII then Result := 'ASCII' - else if AEncoding = TEncoding.BigEndianUnicode then Result :='BigEndianUnicode' - else if AEncoding = TEncoding.Default then Result :='Default' - else if AEncoding = TEncoding.Unicode then Result :='Unicode' - else if AEncoding = TEncoding.UTF7 then Result :='UTF7' - else if AEncoding = TEncoding.UTF8 then Result :='UTF8'; + LMBWriter := T.Create; + LMBWriter.WriteTo(AValue, AMediaType, AOutputStream, AActivation); end; + { TMARSMessageBodyRegistry } class destructor TMARSMessageBodyRegistry.ClassDestructor; @@ -170,6 +182,13 @@ procedure TMARSMessageBodyRegistry.Enumerate(const AProc: TProc); procedure TMARSMessageBodyRegistry.FindWriter(const AActivation: IMARSActivation; out AWriter: IMessageBodyWriter; out AMediaType: TMediaType); +begin + FindWriter(AActivation, AActivation.Request.Accept, AWriter, AMediaType); +end; + +procedure TMARSMessageBodyRegistry.FindWriter( + const AActivation: IMARSActivation; const AAccept: string; + out AWriter: IMessageBodyWriter; out AMediaType: TMediaType); var LWriterEntry: TEntryInfo; LFound: Boolean; @@ -185,11 +204,10 @@ procedure TMARSMessageBodyRegistry.FindWriter(const AActivation: IMARSActivation LMediaType: string; LCandidateMediaType: string; LCandidateQualityFactor: Double; - LAccept: string; LMethodReturnType: TRttiType; LMethodAttributes: TArray; + LAffinity: Integer; begin - LAccept := AActivation.Request.Accept; LMethodReturnType := AActivation.MethodReturnType; LMethodAttributes := AActivation.MethodAttributes; @@ -204,7 +222,7 @@ procedure TMARSMessageBodyRegistry.FindWriter(const AActivation: IMARSActivation Exit; // no serialization (it's a procedure!) // consider client's Accept - LAcceptParser := TAcceptParser.Create(LAccept); + LAcceptParser := TAcceptParser.Create(AAccept); try LAcceptMediaTypes := LAcceptParser.MediaTypeList; @@ -241,12 +259,15 @@ procedure TMARSMessageBodyRegistry.FindWriter(const AActivation: IMARSActivation else LMediaTypes := TMediaTypeList.Intersect(LAllowedMediaTypes, LWriterMediaTypes); for LMediaType in LMediaTypes do + begin + LAffinity := LWriterEntry.GetAffinity(LMethodReturnType, LMethodAttributes, LMediaType); if LWriterEntry.IsWritable(LMethodReturnType, LMethodAttributes, LMediaType) then begin if not LFound + or (LCandidateAffinity < LAffinity) or ( - (LCandidateAffinity < LWriterEntry.GetAffinity(LMethodReturnType, LMethodAttributes, LMediaType)) - or (LCandidateQualityFactor < LAcceptMediaTypes.GetQualityFactor(LMediaType)) + (LCandidateAffinity = LAffinity) + and (LCandidateQualityFactor < LAcceptMediaTypes.GetQualityFactor(LMediaType)) ) then begin @@ -257,6 +278,7 @@ procedure TMARSMessageBodyRegistry.FindWriter(const AActivation: IMARSActivation LFound := True; end; end; + end; finally LWriterMediaTypes.Free; end; diff --git a/Source/MARS.Core.MessageBodyWriters.pas b/Source/MARS.Core.MessageBodyWriters.pas index 2167f7c1..ffb6702c 100644 --- a/Source/MARS.Core.MessageBodyWriters.pas +++ b/Source/MARS.Core.MessageBodyWriters.pas @@ -38,7 +38,18 @@ TJSONValueWriter = class(TInterfacedObject, IMessageBodyWriter) AOutputStream: TStream; const AActivation: IMARSActivation); class procedure WriteJSONValue(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); inline; + end; + + [Produces(TMediaType.APPLICATION_XML)] + TXMLWriter = class(TInterfacedObject, IMessageBodyWriter) + protected + public + procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); + + class procedure WriteXML(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); inline; end; [Produces(TMediaType.APPLICATION_JSON)] @@ -86,11 +97,9 @@ TPrimitiveTypesWriter = class(TInterfacedObject, IMessageBodyWriter) implementation uses - System.TypInfo - , MARS.Core.JSON - , MARS.Core.Utils - , MARS.Rtti.Utils - ; + System.TypInfo, Xml.XMLIntf, System.JSON +, MARS.Core.JSON, MARS.Core.Utils, MARS.Rtti.Utils +; { TObjectWriter } @@ -140,15 +149,8 @@ procedure TArrayOfObjectWriter.WriteTo(const AValue: TValue; class procedure TJSONValueWriter.WriteJSONValue(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); -var - LJSONWriter: TJSONValueWriter; begin - LJSONWriter := TJSONValueWriter.Create; - try - LJSONWriter.WriteTo(AValue, AMediaType, AOutputStream, AActivation); - finally - LJSONWriter.Free; - end; + TMARSMessageBodyWriter.WriteWith(AValue, AMediaType, AOutputStream, AActivation); end; procedure TJSONValueWriter.WriteTo(const AValue: TValue; const AMediaType: TMediaType; @@ -164,7 +166,7 @@ procedure TJSONValueWriter.WriteTo(const AValue: TValue; const AMediaType: TMedi LEncoding: TEncoding; LContentBytes: TBytes; begin - if not GetDesiredEncoding(AActivation, LEncoding) then + if not TMARSMessageBodyWriter.GetDesiredEncoding(AActivation, LEncoding) then LEncoding := TEncoding.UTF8; // UTF8 by default LJSONString := ''; @@ -391,7 +393,7 @@ procedure TPrimitiveTypesWriter.WriteTo(const AValue: TValue; LContentType: string; LEncodingName: string; begin - if not GetDesiredEncoding(AActivation, LEncoding) then + if not TMARSMessageBodyWriter.GetDesiredEncoding(AActivation, LEncoding) then LEncoding := TEncoding.UTF8; // UTF8 by default LEncodingName := GetEncodingName(LEncoding); @@ -413,6 +415,48 @@ procedure TPrimitiveTypesWriter.WriteTo(const AValue: TValue; AOutputStream.Write(LContentBytes, Length(LContentBytes)); end; +{ TXMLWriter } + +procedure TXMLWriter.WriteTo(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); +var + LEncoding: TEncoding; + LContentBytes: TBytes; + LXMLDocument: IXMLDocument; + LXMLString: string; + LXMLNode: IXMLNode; +begin + if not TMARSMessageBodyWriter.GetDesiredEncoding(AActivation, LEncoding) then + LEncoding := TEncoding.UTF8; // UTF8 by default + + LXMLString := ''; + if AValue.IsType then + LXMLString := AValue.AsType + else if AValue.IsType then + begin + LXMLDocument := AValue.AsType; + LXMLDocument.SaveToXML(LXMLString); + end + else if AValue.IsType then + begin + LXMLNode := AValue.AsType; + LXMLString := LXMLNode.XML; + end; + + if LXMLString = '' then + Exit; + + LContentBytes := LEncoding.GetBytes(LXMLString); + AOutputStream.Write(LContentBytes, Length(LContentBytes)); +end; + +class procedure TXMLWriter.WriteXML(const AValue: TValue; + const AMediaType: TMediaType; AOutputStream: TStream; + const AActivation: IMARSActivation); +begin + TMARSMessageBodyWriter.WriteWith(AValue, AMediaType, AOutputStream, AActivation); +end; + procedure RegisterWriters; begin TMARSMessageBodyRegistry.Instance.RegisterWriter(TJSONValueWriter); @@ -428,6 +472,9 @@ procedure RegisterWriters; end ); + TMARSMessageBodyRegistry.Instance.RegisterWriter(TXMLWriter); + TMARSMessageBodyRegistry.Instance.RegisterWriter(TXMLWriter); + TMARSMessageBodyRegistry.Instance.RegisterWriter(TStreamValueWriter); TMARSMessageBodyRegistry.Instance.RegisterWriter( diff --git a/Source/MARS.Core.RequestAndResponse.Interfaces.pas b/Source/MARS.Core.RequestAndResponse.Interfaces.pas new file mode 100644 index 00000000..59ac5118 --- /dev/null +++ b/Source/MARS.Core.RequestAndResponse.Interfaces.pas @@ -0,0 +1,76 @@ +unit MARS.Core.RequestAndResponse.Interfaces; + +interface + +uses + Classes, SysUtils; + +type + IMARSRequest = interface + function GetRawContent: TBytes; + function GetContent: string; + function GetQueryParamIndex(const AName: string): Integer; + function GetQueryParamValue(const AIndex: Integer): string; + function GetQueryParamCount: Integer; + function GetFormParamIndex(const AName: string): Integer; + function GetFormParamName(const AIndex: Integer): string; + function GetFormParamValue(const AIndex: Integer): string; overload; + function GetFormParamValue(const AName: string): string; overload; + function GetFormFileParamIndex(const AName: string): Integer; + function GetFormFileParam(const AIndex: Integer; out AFieldName, AFileName: string; + out ABytes: TBytes; out AContentType: string): Boolean; + function GetFormParamCount: Integer; + function GetHeaderParamValue(const AHeaderName: string): string; + function GetCookieParamIndex(const AName: string): Integer; + function GetCookieParamValue(const AIndex: Integer): string; overload; + function GetCookieParamValue(const AName: string): string; overload; + function GetCookieParamCount: Integer; + function GetFilesCount: Integer; + function GetFormParams: string; + function GetAccept: string; + function GetAuthorization: string; + function GetMethod: string; + function GetQueryString: string; + function GetHostName: string; + function GetPort: Integer; + function GetRawPath: string; + + function AsObject: TObject; + procedure CheckWorkaroundForISAPI; + + property RawContent: TBytes read GetRawContent; + property Content: string read GetContent; + property Accept: string read GetAccept; + property Authorization: string read GetAuthorization; + property Method: string read GetMethod; + property QueryString: string read GetQueryString; + property HostName: string read GetHostName; + property Port: Integer read GetPort; + property RawPath: string read GetRawPath; + end; + + IMARSResponse = interface + function GetContentStream: TStream; + procedure SetContentStream(const AContentStream: TStream); + function GetContentType: string; + procedure SetContentType(const AContentType: string); + function GetContentEncoding: string; + procedure SetContentEncoding(const AContentEncoding: string); + function GetStatusCode: Integer; + procedure SetStatusCode(const AStatusCode: Integer); + function GetContent: string; + procedure SetContent(const AContent: string); + procedure SetHeader(const AName, AValue: string); + procedure SetCookie(const AName, AValue, ADomain, APath: string; const AExpiration: TDateTime; const ASecure: Boolean); + + property Content: string read GetContent write SetContent; + property ContentStream: TStream read GetContentStream write SetContentStream; + property ContentType: string read GetContentType write SetContentType; + property ContentEncoding: string read GetContentEncoding write SetContentEncoding; + property StatusCode: Integer read GetStatusCode write SetStatusCode; + end; + + +implementation + +end. diff --git a/Source/MARS.Core.Response.pas b/Source/MARS.Core.Response.pas index 0e3b9c08..dd5734b8 100644 --- a/Source/MARS.Core.Response.pas +++ b/Source/MARS.Core.Response.pas @@ -9,8 +9,7 @@ interface uses SysUtils, Classes - , HTTPApp - , MARS.Core.MediaType; +, MARS.Core.RequestAndResponse.Interfaces, MARS.Core.MediaType; type @@ -23,7 +22,7 @@ TMARSResponse = class FContentStream: TStream; FFreeContentStream: Boolean; public - procedure CopyTo(AWebResponse: TWebResponse); + procedure CopyTo(AResponse: IMARSResponse); destructor Destroy; override; property Content: string read FContent write FContent; @@ -40,19 +39,19 @@ implementation { TMARSResponse } -procedure TMARSResponse.CopyTo(AWebResponse: TWebResponse); +procedure TMARSResponse.CopyTo(AResponse: IMARSResponse); begin if Assigned(ContentStream) then begin - AWebResponse.ContentStream := ContentStream; + AResponse.ContentStream := ContentStream; FreeContentStream := False; end else - AWebResponse.Content := Content; + AResponse.Content := Content; - AWebResponse.ContentType := ContentType; - AWebResponse.ContentEncoding := ContentEncoding; - AWebResponse.StatusCode := StatusCode; + AResponse.ContentType := ContentType; + AResponse.ContentEncoding := ContentEncoding; + AResponse.StatusCode := StatusCode; end; destructor TMARSResponse.Destroy; diff --git a/Source/MARS.Core.Token.pas b/Source/MARS.Core.Token.pas index 97d66394..03e8a83b 100644 --- a/Source/MARS.Core.Token.pas +++ b/Source/MARS.Core.Token.pas @@ -11,10 +11,9 @@ interface uses SysUtils, Classes, Generics.Collections, SyncObjs, Rtti -, HTTPApp, IdGlobal +, IdGlobal -, MARS.Core.URL -, MARS.Utils.Parameters +, MARS.Core.URL, MARS.Utils.Parameters, MARS.Core.RequestAndResponse.Interfaces //{$IFDEF mORMot-JWT} @@ -39,8 +38,8 @@ TMARSToken = class FCookieDomain: string; FCookiePath: string; FCookieSecure: Boolean; - FRequest: TWebRequest; - FResponse: TWebResponse; + FRequest: IMARSRequest; + FResponse: IMARSResponse; FDuration: TDateTime; FIssuer: string; function GetUserName: string; @@ -52,22 +51,22 @@ TMARSToken = class function GetDurationMins: Int64; function GetDurationSecs: Int64; protected - function GetTokenFromBearer(const ARequest: TWebRequest): string; virtual; - function GetTokenFromCookie(const ARequest: TWebRequest): string; virtual; - function GetToken(const ARequest: TWebRequest): string; virtual; + function GetTokenFromBearer(const ARequest: IMARSRequest): string; virtual; + function GetTokenFromCookie(const ARequest: IMARSRequest): string; virtual; + function GetToken(const ARequest: IMARSRequest): string; virtual; function GetIsExpired: Boolean; virtual; function BuildJWTToken(const ASecret: string; const AClaims: TMARSParameters): string; virtual; function LoadJWTToken(const AToken: string; const ASecret: string; var AClaims: TMARSParameters): Boolean; virtual; - property Request: TWebRequest read FRequest; - property Response: TWebResponse read FResponse; + property Request: IMARSRequest read FRequest; + property Response: IMARSResponse read FResponse; public constructor Create(); reintroduce; overload; virtual; constructor Create(const AToken: string; const AParameters: TMARSParameters); overload; virtual; constructor Create(const AToken: string; const ASecret: string; const AIssuer: string; const ADuration: TDateTime); overload; virtual; - constructor Create(const ARequest: TWebRequest; const AResponse: TWebResponse; + constructor Create(const ARequest: IMARSRequest; const AResponse: IMARSResponse; const AParameters: TMARSParameters; const AURL: TMARSURL); overload; virtual; destructor Destroy; override; @@ -176,7 +175,7 @@ constructor TMARSToken.Create(const AToken, ASecret, AIssuer: string; Load(AToken, ASecret); end; -constructor TMARSToken.Create(const ARequest: TWebRequest; const AResponse: TWebResponse; +constructor TMARSToken.Create(const ARequest: IMARSRequest; const AResponse: IMARSResponse; const AParameters: TMARSParameters; const AURL: TMARSURL); begin FRequest := ARequest; @@ -254,7 +253,7 @@ function TMARSToken.GetRoles: TArray; {$endif} end; -function TMARSToken.GetToken(const ARequest: TWebRequest): string; +function TMARSToken.GetToken(const ARequest: IMARSRequest): string; begin // Beware: First match wins! @@ -265,7 +264,7 @@ function TMARSToken.GetToken(const ARequest: TWebRequest): string; Result := GetTokenFromCookie(ARequest); end; -function TMARSToken.GetTokenFromBearer(const ARequest: TWebRequest): string; +function TMARSToken.GetTokenFromBearer(const ARequest: IMARSRequest): string; var LAuth: string; LAuthTokens: TArray; @@ -293,12 +292,12 @@ function TMARSToken.GetTokenFromBearer(const ARequest: TWebRequest): string; Result := LAuthTokens[1]; end; -function TMARSToken.GetTokenFromCookie(const ARequest: TWebRequest): string; +function TMARSToken.GetTokenFromCookie(const ARequest: IMARSRequest): string; begin Result := ''; if CookieEnabled and (CookieName <> '') then {$ifdef DelphiXE7_UP} - Result := TNetEncoding.URL.Decode(ARequest.CookieFields.Values[CookieName]); + Result := TNetEncoding.URL.Decode(ARequest.GetCookieParamValue(CookieName)); {$else} Result := TIdURI.URLDecode(ARequest.CookieFields.Values[CookieName]); {$endif} @@ -381,33 +380,15 @@ procedure TMARSToken.SetUserNameAndRoles(const AUserName: string; end; procedure TMARSToken.UpdateCookie; -var - LContent: TStringList; begin if CookieEnabled then begin Assert(Assigned(Response)); - LContent := TStringList.Create; - try - if IsVerified and not IsExpired then - begin - LContent.Values[CookieName] := Token; - - Response.SetCookieField( - LContent, CookieDomain, CookiePath, Expiration, CookieSecure); - end - else begin - if Request.CookieFields.Values[CookieName] <> '' then - begin - LContent.Values[CookieName] := 'dummy'; - Response.SetCookieField( - LContent, CookieDomain, CookiePath, Now-1, CookieSecure); - end; - end; - finally - LContent.Free; - end; + if IsVerified and not IsExpired then + Response.SetCookie(CookieName, Token, CookieDomain, CookiePath, Expiration, CookieSecure) + else if Request.GetCookieParamValue(CookieName) <> '' then + Response.SetCookie(CookieName, 'dummy', CookieDomain, CookiePath, Now-1, CookieSecure); end; end; diff --git a/Source/MARS.Core.URL.pas b/Source/MARS.Core.URL.pas index 675573ff..7b6f365a 100644 --- a/Source/MARS.Core.URL.pas +++ b/Source/MARS.Core.URL.pas @@ -13,10 +13,9 @@ interface uses - Classes, SysUtils - , MARS.Core.JSON - , HTTPApp - , Generics.Collections + Classes, SysUtils, System.JSON +, MARS.Core.JSON, MARS.Core.RequestAndResponse.Interfaces +, Generics.Collections ; type @@ -61,7 +60,7 @@ TMARSURL = class constructor Create(const AURL: string); overload; virtual; constructor CreateDummy(const APath: string; const ABaseURL: string = DUMMY_URL); overload; virtual; constructor CreateDummy(const APaths: array of string; const ABaseURL: string = DUMMY_URL); overload; virtual; - constructor Create(AWebRequest: TWebRequest); overload; virtual; + constructor Create(ARequest: IMARSRequest); overload; virtual; destructor Destroy; override; function MatchPath(AOtherURL: TMARSURL; const ACaseSensitive: Boolean = False): Boolean; overload; virtual; @@ -202,16 +201,16 @@ class function TMARSURL.CombinePath(const APathTokens: array of string; Result := EnsureLastPathDelimiter(Result); end; -constructor TMARSURL.Create(AWebRequest: TWebRequest); +constructor TMARSURL.Create(ARequest: IMARSRequest); var LQuery: string; begin - LQuery := string(AWebRequest.Query); + LQuery := ARequest.QueryString; if LQuery <> '' then LQuery := URL_QUERY_PREFIX + LQuery; // Add the protocol in order to make Parse work. - Create('http://' + string(AWebRequest.Host) + ':' + IntToStr(AWebRequest.ServerPort) + string(AWebRequest.RawPathInfo) + LQuery); + Create('http://' + ARequest.HostName + ':' + ARequest.Port.ToString + ARequest.RawPath + LQuery); end; constructor TMARSURL.CreateDummy(const APaths: array of string; const ABaseURL: string); diff --git a/Source/MARS.Core.Utils.pas b/Source/MARS.Core.Utils.pas index e013b020..689b7ad5 100644 --- a/Source/MARS.Core.Utils.pas +++ b/Source/MARS.Core.Utils.pas @@ -10,8 +10,8 @@ interface uses - SysUtils, Classes, RTTI, SyncObjs, Web.HttpApp, REST.JSON -, MARS.Core.JSON + SysUtils, Classes, RTTI, SyncObjs, REST.JSON, System.JSON +, MARS.Core.JSON, MARS.Core.RequestAndResponse.Interfaces ; type @@ -21,8 +21,8 @@ TFormParamFile = record Bytes: TBytes; ContentType: string; procedure Clear; - constructor CreateFromRequest(const ARequest: TWebRequest; const AFieldName: string); overload; - constructor CreateFromRequest(const ARequest: TWebRequest; const AFileIndex: Integer); overload; + constructor CreateFromRequest(const ARequest: IMARSRequest; const AFieldName: string); overload; + constructor CreateFromRequest(const ARequest: IMARSRequest; const AFileIndex: Integer); overload; constructor Create(const AFieldName: string; const AFileName: string; const ABytes: TBytes; const AContentType: string); function ToString: string; end; @@ -33,8 +33,8 @@ TFormParam = record function IsFile: Boolean; function AsFile: TFormParamFile; procedure Clear; - constructor CreateFromRequest(const ARequest: TWebRequest; const AFieldName: string); overload; - constructor CreateFromRequest(const ARequest: TWebRequest; const AFileIndex: Integer); overload; + constructor CreateFromRequest(const ARequest: IMARSRequest; const AFieldName: string); overload; + constructor CreateFromRequest(const ARequest: IMARSRequest; const AFileIndex: Integer); overload; constructor Create(const AFieldName: string; const AValue: TValue); constructor CreateFile(const AFieldName: string; const AFileName: string; const ABytes: TBytes = nil; const AContentType: string = ''); function ToString: string; @@ -42,7 +42,7 @@ TFormParam = record TDump = class public - class procedure Request(const ARequest: TWebRequest; const AFileName: string); overload; virtual; + class procedure Request(const ARequest: IMARSRequest; const AFileName: string); overload; virtual; end; @@ -75,7 +75,7 @@ TDump = class {$endif} function DateToJSON(const ADate: TDateTime; AInputIsUTC: Boolean = False): string; - function JSONToDate(const ADate: string; AReturnUTC: Boolean = False): TDateTime; + function JSONToDate(const ADate: string; AReturnUTC: Boolean = False; const ADefault: TDateTime = 0.0): TDateTime; function IsMask(const AString: string): Boolean; function MatchesMask(const AString, AMask: string): Boolean; @@ -83,13 +83,15 @@ TDump = class function GuessTValueFromString(const AString: string): TValue; function TValueToString(const AValue: TValue; const ARecursion: Integer = 0): string; - procedure ZipStream(const ASource: TStream; const ADest: TStream); - procedure UnzipStream(const ASource: TStream; const ADest: TStream); + procedure ZipStream(const ASource: TStream; const ADest: TStream; const WindowBits: Integer = 15); + procedure UnzipStream(const ASource: TStream; const ADest: TStream; const WindowBits: Integer = 15); function StreamToBase64(const AStream: TStream): string; procedure Base64ToStream(const ABase64: string; const ADestStream: TStream); function StreamToBytes(const ASource: TStream): TBytes; + function GetEncodingName(const AEncoding: TEncoding): string; + implementation uses @@ -100,6 +102,20 @@ implementation , StrUtils, DateUtils, Masks, ZLib, Zip, NetEncoding ; +function GetEncodingName(const AEncoding: TEncoding): string; +begin + Result := ''; + + if AEncoding = TEncoding.ANSI then Result := 'ANSI' + else if AEncoding = TEncoding.ASCII then Result := 'ASCII' + else if AEncoding = TEncoding.BigEndianUnicode then Result :='BigEndianUnicode' + else if AEncoding = TEncoding.Unicode then Result :='Unicode' + else if AEncoding = TEncoding.UTF7 then Result :='UTF7' + else if AEncoding = TEncoding.UTF8 then Result :='UTF8' + else if AEncoding = TEncoding.Default then Result :='Default'; +end; + + function StreamToBytes(const ASource: TStream): TBytes; begin SetLength(Result, ASource.Size); @@ -108,14 +124,14 @@ function StreamToBytes(const ASource: TStream): TBytes; raise Exception.Create('Unable to copy all content to TBytes'); end; -procedure ZipStream(const ASource: TStream; const ADest: TStream); +procedure ZipStream(const ASource: TStream; const ADest: TStream; const WindowBits: Integer = 15); var LZipStream: TZCompressionStream; begin Assert(Assigned(ASource)); Assert(Assigned(ADest)); - LZipStream := TZCompressionStream.Create(clDefault, ADest); + LZipStream := TZCompressionStream.Create(ADest, TZCompressionLevel.zcDefault, WindowBits); try ASource.Position := 0; LZipStream.CopyFrom(ASource, ASource.Size); @@ -124,14 +140,14 @@ procedure ZipStream(const ASource: TStream; const ADest: TStream); end; end; -procedure UnzipStream(const ASource: TStream; const ADest: TStream); +procedure UnzipStream(const ASource: TStream; const ADest: TStream; const WindowBits: Integer = 15); var LZipStream: TZDecompressionStream; begin Assert(Assigned(ASource)); Assert(Assigned(ADest)); - LZipStream := TZDecompressionStream.Create(ASource); + LZipStream := TZDecompressionStream.Create(ASource, WindowBits); try ASource.Position := 0; ADest.CopyFrom(LZipStream, LZipStream.Size); @@ -315,9 +331,9 @@ function DateToJSON(const ADate: TDateTime; AInputIsUTC: Boolean = False): strin Result := DateToISO8601(ADate, AInputIsUTC); end; -function JSONToDate(const ADate: string; AReturnUTC: Boolean = False): TDateTime; +function JSONToDate(const ADate: string; AReturnUTC: Boolean = False; const ADefault: TDateTime = 0.0): TDateTime; begin - Result := 0.0; + Result := ADefault; if ADate<>'' then Result := ISO8601ToDate(ADate, AReturnUTC); end; @@ -474,24 +490,9 @@ procedure TFormParamFile.Clear; ContentType := ''; end; -constructor TFormParamFile.CreateFromRequest(const ARequest: TWebRequest; const AFieldName: string); -var - LIndex, LFileIndex: Integer; - LFile: TAbstractWebRequestFile; +constructor TFormParamFile.CreateFromRequest(const ARequest: IMARSRequest; const AFieldName: string); begin - Clear; - LFileIndex := -1; - for LIndex := 0 to ARequest.Files.Count - 1 do - begin - LFile := ARequest.Files[LIndex]; - if SameText(LFile.FieldName, AFieldName) then - begin - LFileIndex := LIndex; - Break; - end; - end; - - CreateFromRequest(ARequest, LFileIndex); + CreateFromRequest(ARequest, ARequest.GetFormFileParamIndex(AFieldName)); end; constructor TFormParamFile.Create(const AFieldName, AFileName: string; @@ -503,18 +504,16 @@ constructor TFormParamFile.Create(const AFieldName, AFileName: string; ContentType := AContentType; end; -constructor TFormParamFile.CreateFromRequest(const ARequest: TWebRequest; +constructor TFormParamFile.CreateFromRequest(const ARequest: IMARSRequest; const AFileIndex: Integer); var - LFile: TAbstractWebRequestFile; + LFieldName, LFileName, LContentType: string; + LBytes: TBytes; begin - Clear; - if (AFileIndex >= 0) and (AFileIndex < ARequest.Files.Count) then - begin - LFile := ARequest.Files[AFileIndex]; - - Create(LFile.FieldName, LFile.FileName, StreamToBytes(LFile.Stream), LFile.ContentType); - end; + if ARequest.GetFormFileParam(AFileIndex, LFieldName, LFileName, LBytes, LContentType) then + Create(LFieldName, LFileName, LBytes, LContentType) + else + raise Exception.CreateFmt('Unable to extract data for file form param index %d', [AFileIndex]); end; function TFormParamFile.ToString: string; @@ -552,7 +551,7 @@ constructor TFormParam.CreateFile(const AFieldName, AFileName: string; ); end; -constructor TFormParam.CreateFromRequest(const ARequest: TWebRequest; +constructor TFormParam.CreateFromRequest(const ARequest: IMARSRequest; const AFileIndex: Integer); var LValue: TFormParamFile; @@ -563,17 +562,17 @@ constructor TFormParam.CreateFromRequest(const ARequest: TWebRequest; FieldName := LValue.FieldName; end; -constructor TFormParam.CreateFromRequest(const ARequest: TWebRequest; +constructor TFormParam.CreateFromRequest(const ARequest: IMARSRequest; const AFieldName: string); var LIndex: Integer; begin Clear; - LIndex := ARequest.ContentFields.IndexOfName(AFieldName); + LIndex := ARequest.GetFormParamIndex(AFieldName); if LIndex <> -1 then begin FieldName := AFieldName; - Value := ARequest.ContentFields.ValueFromIndex[LIndex]; + Value := ARequest.GetFormParamValue(LIndex); end else begin @@ -599,7 +598,7 @@ function TFormParam.ToString: string; { TDump } -class procedure TDump.Request(const ARequest: TWebRequest; +class procedure TDump.Request(const ARequest: IMARSRequest; const AFileName: string); var LSS: TStringStream; @@ -634,24 +633,23 @@ class procedure TDump.Request(const ARequest: TWebRequest; end; LHeaders := string.join(sLineBreak, [ - 'PathInfo: ' + ARequest.PathInfo + 'RawPath: ' + ARequest.RawPath , 'Method: ' + ARequest.Method - , 'ProtocolVersion: ' + ARequest.ProtocolVersion , 'Authorization: ' + ARequest.Authorization , 'Accept: ' + ARequest.Accept - , 'ContentFields: ' + ARequest.ContentFields.CommaText - , 'CookieFields: ' + ARequest.CookieFields.CommaText - , 'QueryFields: ' + ARequest.QueryFields.CommaText +// , 'ContentFields: ' + ARequest.ContentFields.CommaText +// , 'CookieFields: ' + ARequest.CookieFields.CommaText +// , 'QueryFields: ' + ARequest.QueryFields.CommaText - , 'ContentType: ' + ARequest.ContentType - , 'ContentEncoding: ' + ARequest.ContentEncoding - , 'ContentLength: ' + ARequest.ContentLength.ToString - , 'ContentVersion: ' + ARequest.ContentVersion +// , 'ContentType: ' + ARequest.ContentType +// , 'ContentEncoding: ' + ARequest.ContentEncoding +// , 'ContentLength: ' + ARequest.ContentLength.ToString +// , 'ContentVersion: ' + ARequest.ContentVersion - , 'RemoteAddr: ' + ARequest.RemoteAddr - , 'RemoteHost: ' + ARequest.RemoteHost - , 'RemoteIP: ' + ARequest.RemoteIP +// , 'RemoteAddr: ' + ARequest.RemoteAddr +// , 'RemoteHost: ' + ARequest.RemoteHost +// , 'RemoteIP: ' + ARequest.RemoteIP ]); LSS := TStringStream.Create(LHeaders + sLineBreak + sLineBreak + LRawString); diff --git a/Source/MARS.Data.FireDAC.ReadersAndWriters.pas b/Source/MARS.Data.FireDAC.ReadersAndWriters.pas index defef415..7f04f750 100644 --- a/Source/MARS.Data.FireDAC.ReadersAndWriters.pas +++ b/Source/MARS.Data.FireDAC.ReadersAndWriters.pas @@ -10,19 +10,13 @@ interface uses - Classes, SysUtils, Rtti + Classes, SysUtils, Rtti, DB - , DB - - , MARS.Core.Attributes - , MARS.Core.Activation.Interfaces - , MARS.Core.Declarations - , MARS.Core.MediaType - , MARS.Core.Classes - , MARS.Core.MessageBodyWriter - , MARS.Core.MessageBodyReader - , MARS.Core.Utils - , MARS.Data.Utils; +, MARS.Core.Attributes, MARS.Core.Activation.Interfaces, MARS.Core.Declarations +, MARS.Core.MediaType, MARS.Core.Classes +, MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyReader +, MARS.Core.Utils, MARS.Data.Utils +; type // --- READERS --- @@ -37,10 +31,9 @@ TArrayFDMemTableReader = class(TInterfacedObject, IMessageBodyReader) end; // --- WRITERS --- - [ Produces(TMediaType.APPLICATION_XML) - , Produces(TMediaType.APPLICATION_JSON_FIREDAC) - , Produces(TMediaType.APPLICATION_OCTET_STREAM) - ] + [ Produces(TMediaType.APPLICATION_JSON_FIREDAC) + , Produces(TMediaType.APPLICATION_XML_FIREDAC) + , Produces(TMediaType.APPLICATION_OCTET_STREAM) ] TFDDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); @@ -50,6 +43,8 @@ TFDDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) TArrayFDDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); + class procedure WriteDataSet(const ADataSet: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); class procedure WriteDataSets(const ADataSets: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); end; @@ -58,23 +53,25 @@ TArrayFDDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) implementation uses - Generics.Collections - , FireDAC.Comp.Client, FireDAC.Comp.DataSet - , FireDAC.Stan.Intf - - , FireDAC.Stan.StorageBIN - , FireDAC.Stan.StorageJSON - , FireDAC.Stan.StorageXML - - , MARS.Core.JSON - , MARS.Core.MessageBodyWriters, MARS.Core.MessageBodyReaders - {$ifdef DelphiXE7_UP}, System.JSON {$endif} - , MARS.Core.Exceptions - , MARS.Rtti.Utils, MARS.Data.FireDAC.Utils + Generics.Collections +, FireDAC.Comp.Client, FireDAC.Comp.DataSet, FireDAC.Stan.Intf +, FireDAC.Stan.StorageBIN, FireDAC.Stan.StorageJSON, FireDAC.Stan.StorageXML + +, MARS.Core.JSON {$ifdef DelphiXE7_UP}, System.JSON {$endif} +, MARS.Core.MessageBodyWriters, MARS.Core.MessageBodyReaders, MARS.Data.MessageBodyWriters +, MARS.Core.Exceptions, MARS.Rtti.Utils, MARS.Data.FireDAC.Utils ; { TArrayFDDataSetWriter } +class procedure TArrayFDDataSetWriter.WriteDataSet(const ADataSet: TValue; + const AMediaType: TMediaType; AOutputStream: TStream; + const AActivation: IMARSActivation); +begin + WriteDataSets(TValue.From>([ADataSet.AsType]) + , AMediaType, AOutputStream, AActivation); +end; + class procedure TArrayFDDataSetWriter.WriteDataSets(const ADataSets: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); @@ -111,11 +108,15 @@ procedure TFDDataSetWriter.WriteTo(const AValue: TValue; const AMediaType: TMedi begin LDataset := AValue.AsType; - if AMediaType.Matches(TMediaType.APPLICATION_XML) then + if AMediaType.IsWildcard then + begin + TDataSetWriterJSON.WriteDataSet(LDataSet, AMediaType, AOutputStream, AActivation); + AActivation.Response.ContentType := TMediaType.APPLICATION_JSON; + end + else if AMediaType.Matches(TMediaType.APPLICATION_XML_FIREDAC) then LDataSet.SaveToStream(AOutputStream, sfXML) else if AMediaType.Matches(TMediaType.APPLICATION_JSON_FireDAC) then - TArrayFDDataSetWriter.WriteDataSets(TValue.From>([LDataSet]), - AMediaType, AOutputStream, AActivation) + TArrayFDDataSetWriter.WriteDataSet(LDataSet, AMediaType, AOutputStream, AActivation) else if AMediaType.Matches(TMediaType.APPLICATION_OCTET_STREAM) then LDataSet.SaveToStream(AOutputStream, sfBinary) else diff --git a/Source/MARS.Data.FireDAC.pas b/Source/MARS.Data.FireDAC.pas index 847a7b72..722c64ce 100644 --- a/Source/MARS.Data.FireDAC.pas +++ b/Source/MARS.Data.FireDAC.pas @@ -11,7 +11,6 @@ interface uses System.Classes, System.SysUtils, Generics.Collections, Rtti - , Data.DB // *** BEWARE *** // if your Delphi edition/license does not include FireDAC, @@ -229,8 +228,11 @@ class function TMARSFireDAC.LoadConnectionDefs(const AParameters: TMARSParameter LConnectionParams.CopyFrom(LData, LConnectionDefName); LParams := GetAsTStrings(LConnectionParams); try - FDManager.AddConnectionDef(LConnectionDefName, LParams.Values['DriverID'], LParams); - Result := Result + [LConnectionDefName]; + if FDManager.ConnectionDefs.FindConnectionDef(LConnectionDefName) = nil then + begin + FDManager.AddConnectionDef(LConnectionDefName, LParams.Values['DriverID'], LParams); + Result := Result + [LConnectionDefName]; + end; finally LParams.Free; end; @@ -573,9 +575,9 @@ class function TMARSFireDAC.GetContextValue(const AName: string; const AActivati else if SameText(LFirstToken, 'QueryParam') then Result := AActivation.URL.QueryTokenByName(LSecondTokenAndAll) else if SameText(LFirstToken, 'FormParam') then - Result := AActivation.Request.ContentFields.Values[LSecondTokenAndAll] + Result := AActivation.Request.GetFormParamValue(LSecondTokenAndAll) else if SameText(LFirstToken, 'Request') then - Result := ReadPropertyValue(AActivation.Request, LSecondTokenAndAll) + Result := ReadPropertyValue(AActivation.Request.AsObject, LSecondTokenAndAll) // else if SameText(LFirstToken, 'Response') then // Result := ReadPropertyValue(AActivation.Response, LSecondToken) else if SameText(LFirstToken, 'URL') then @@ -599,6 +601,9 @@ procedure TMARSFireDAC.InjectMacroValues(const ACommand: TFDCustomCommand; const LIndex: Integer; LMacro: TFDMacro; begin + if not Assigned(ACommand) then + Exit; + for LIndex := 0 to ACommand.Macros.Count-1 do begin LMacro := ACommand.Macros[LIndex]; @@ -612,6 +617,8 @@ procedure TMARSFireDAC.InjectParamValues(const ACommand: TFDCustomCommand; const LIndex: Integer; LParam: TFDParam; begin + if not Assigned(ACommand) then + Exit; for LIndex := 0 to ACommand.Params.Count-1 do begin LParam := ACommand.Params[LIndex]; @@ -681,4 +688,7 @@ procedure TMARSFDMemTable.DoUpdateErrorHandler(ARow: TFDDatSRow; FApplyUpdatesRes.AddError(ARow, AException, ARequest); end; +initialization + FDManager.SilentMode := True; + end. diff --git a/Source/MARS.Data.MessageBodyWriters.pas b/Source/MARS.Data.MessageBodyWriters.pas index 10584e19..b82b6b81 100644 --- a/Source/MARS.Data.MessageBodyWriters.pas +++ b/Source/MARS.Data.MessageBodyWriters.pas @@ -21,6 +21,8 @@ interface TDataSetWriterJSON = class(TInterfacedObject, IMessageBodyWriter) procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); + class procedure WriteDataSet(const ADataSet: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); end; [Produces(TMediaType.APPLICATION_JSON)] @@ -33,6 +35,8 @@ TArrayDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) TDataSetWriterXML = class(TInterfacedObject, IMessageBodyWriter) procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); + class procedure WriteDataSet(const ADataSet: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); end; implementation @@ -46,6 +50,20 @@ implementation { TDataSetWriterJSON } +class procedure TDataSetWriterJSON.WriteDataSet(const ADataSet: TValue; + const AMediaType: TMediaType; AOutputStream: TStream; + const AActivation: IMARSActivation); +var + LWriter: TDataSetWriterJSON; +begin + LWriter := TDataSetWriterJSON.Create; + try + LWriter.WriteTo(ADataSet, AMediaType, AOutputStream, AActivation); + finally + LWriter.Free; + end; +end; + procedure TDataSetWriterJSON.WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); var @@ -61,13 +79,27 @@ procedure TDataSetWriterJSON.WriteTo(const AValue: TValue; const AMediaType: TMe { TDataSetWriterXML } +class procedure TDataSetWriterXML.WriteDataSet(const ADataSet: TValue; + const AMediaType: TMediaType; AOutputStream: TStream; + const AActivation: IMARSActivation); +var + LWriter: TDataSetWriterXML; +begin + LWriter := TDataSetWriterXML.Create; + try + LWriter.WriteTo(ADataSet, AMediaType, AOutputStream, AActivation); + finally + LWriter.Free; + end; +end; + procedure TDataSetWriterXML.WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); var LEncoding: TEncoding; LXML: string; begin - if not GetDesiredEncoding(AActivation, LEncoding) then + if not TMARSMessageBodyWriter.GetDesiredEncoding(AActivation, LEncoding) then LEncoding := TEncoding.UTF8; // UTF8 by default if AValue.AsObject is TClientDataSet then // CDS diff --git a/Source/MARS.Data.UniDAC.InjectionService.pas b/Source/MARS.Data.UniDAC.InjectionService.pas new file mode 100644 index 00000000..ad411daf --- /dev/null +++ b/Source/MARS.Data.UniDAC.InjectionService.pas @@ -0,0 +1,142 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) +unit MARS.Data.UniDAC.InjectionService; + +{$I MARS.inc} + +interface + +uses + Classes, SysUtils, Rtti, Types + , MARS.Core.Injection + , MARS.Core.Injection.Interfaces + , MARS.Core.Injection.Types + , MARS.Core.Activation.Interfaces +; + +type + TMARSUniDACInjectionService = class(TInterfacedObject, IMARSInjectionService) + protected + function GetConnectionDefName(const ADestination: TRttiObject; + const AActivation: IMARSActivation): string; + public + procedure GetValue(const ADestination: TRttiObject; + const AActivation: IMARSActivation; out AValue: TInjectionValue); + + const UniDAC_ConnectionDefName_PARAM = 'UniDAC.ConnectionDefName'; + const UniDAC_ConnectionExpandMacros_PARAM = 'UniDAC.ConnectionExpandMacros'; + const UniDAC_ConnectionDefName_PARAM_DEFAULT = 'MAIN_DB'; + const UniDAC_ConnectionExpandMacros_PARAM_DEFAULT = False; + end; + +implementation + +uses + MARS.Rtti.Utils +, MARS.Core.Token, MARS.Core.URL, MARS.Core.Engine, MARS.Core.Application +, Data.DB +, Uni +, MARS.Data.UniDAC +; + + +{ TMARSUniDACInjectionService } + +function TMARSUniDACInjectionService.GetConnectionDefName( + const ADestination: TRttiObject; + const AActivation: IMARSActivation): string; +var + LConnectionDefName: string; + LExpandMacros: Boolean; +begin + LConnectionDefName := ''; + LExpandMacros := False; + + // field, property or method param annotation + ADestination.HasAttribute( + procedure (AAttrib: ConnectionAttribute) + begin + LConnectionDefName := AAttrib.ConnectionDefName; + LExpandMacros := AAttrib.ExpandMacros; + end + ); + + // second chance: method annotation + if (LConnectionDefName = '') then + AActivation.Method.HasAttribute( + procedure (AAttrib: ConnectionAttribute) + begin + LConnectionDefName := AAttrib.ConnectionDefName; + LExpandMacros := AAttrib.ExpandMacros; + end + ); + + // third chance: resource annotation + if (LConnectionDefName = '') then + AActivation.Resource.HasAttribute( + procedure (AAttrib: ConnectionAttribute) + begin + LConnectionDefName := AAttrib.ConnectionDefName; + LExpandMacros := AAttrib.ExpandMacros; + end + ); + + // last chance: application parameters + if (LConnectionDefName = '') then + begin + LConnectionDefName := AActivation.Application.Parameters.ByName( + UniDAC_ConnectionDefName_PARAM, UniDAC_ConnectionDefName_PARAM_DEFAULT + ).AsString; + LExpandMacros := AActivation.Application.Parameters.ByName( + UniDAC_ConnectionExpandMacros_PARAM, UniDAC_ConnectionExpandMacros_PARAM_DEFAULT + ).AsBoolean; + end; + + if LExpandMacros then + LConnectionDefName := TMARSUniDAC.GetContextValue(LConnectionDefName, AActivation, ftString).AsString; + + Result := LConnectionDefName; +end; + +procedure TMARSUniDACInjectionService.GetValue(const ADestination: TRttiObject; + const AActivation: IMARSActivation; out AValue: TInjectionValue); +begin + if ADestination.GetRttiType.IsObjectOfType(TUniConnection) then + AValue := TInjectionValue.Create( + TMARSUniDAC.CreateConnectionByDefName(GetConnectionDefName(ADestination, AActivation), AActivation) + ) + else if ADestination.GetRttiType.IsObjectOfType(TMARSUniDAC) then + AValue := TInjectionValue.Create( + TMARSUniDAC.Create(GetConnectionDefName(ADestination, AActivation), AActivation) + ); +end; + +procedure RegisterServices; +begin + TMARSInjectionServiceRegistry.Instance.RegisterService( + function :IMARSInjectionService + begin + Result := TMARSUniDACInjectionService.Create; + end + , function (const ADestination: TRttiObject): Boolean + var + LType: TRttiType; + begin + Result := ((ADestination is TRttiParameter) or (ADestination is TRttiField) or (ADestination is TRttiProperty)); + if Result then + begin + LType := ADestination.GetRttiType; + Result := LType.IsObjectOfType(TUniConnection) + or LType.IsObjectOfType(TMARSUniDAC); + end; + end + ); +end; + +initialization + RegisterServices; + +end. \ No newline at end of file diff --git a/Source/MARS.Data.UniDAC.ReadersAndWriters.pas b/Source/MARS.Data.UniDAC.ReadersAndWriters.pas new file mode 100644 index 00000000..ca0ae0f6 --- /dev/null +++ b/Source/MARS.Data.UniDAC.ReadersAndWriters.pas @@ -0,0 +1,177 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) +unit MARS.Data.UniDAC.ReadersAndWriters; + +{$I MARS.inc} + +interface + +uses + Classes, SysUtils, Rtti, DB + // Devart UniDAC + , MemDS, VirtualTable + // MARS + , MARS.Core.Attributes + , MARS.Core.Activation.Interfaces + , MARS.Core.Declarations + , MARS.Core.MediaType + , MARS.Core.Classes + , MARS.Core.MessageBodyWriter + , MARS.Core.MessageBodyReader + , MARS.Core.Utils + , MARS.Data.Utils + , MARS.Data.UniDAC.Utils +; + +type + // --- READERS --- + [Consumes(APPLICATION_JSON_UniDAC)] + TArrayUniMemTableReader = class(TInterfacedObject, IMessageBodyReader) + public + function ReadFrom( + {$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation + ): TValue; virtual; + end; + + // --- WRITERS --- + [ Produces(TMediaType.APPLICATION_XML) + , Produces(APPLICATION_JSON_UniDAC) + , Produces(TMediaType.APPLICATION_OCTET_STREAM) + ] + TUniDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) + procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); + end; + + [Produces(APPLICATION_JSON_UniDAC)] + TArrayUniDataSetWriter = class(TInterfacedObject, IMessageBodyWriter) + procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); + class procedure WriteDataSets(const ADataSets: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); + end; + + +implementation + +uses + Generics.Collections + + , MARS.Core.JSON + , MARS.Core.MessageBodyWriters, MARS.Core.MessageBodyReaders + {$ifdef DelphiXE7_UP}, System.JSON {$endif} + , MARS.Core.Exceptions + , MARS.Rtti.Utils +; + +{ TArrayFDDataSetWriter } + +class procedure TArrayUniDataSetWriter.WriteDataSets(const ADataSets: TValue; + const AMediaType: TMediaType; AOutputStream: TStream; + const AActivation: IMARSActivation); +var + LWriter: TArrayUniDataSetWriter; +begin + LWriter := TArrayUniDataSetWriter.Create; + try + LWriter.WriteTo(ADataSets, AMediaType, AOutputStream, AActivation); + finally + LWriter.Free; + end; +end; + +procedure TArrayUniDataSetWriter.WriteTo(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); +var + LResult: TJSONObject; +begin + LResult := TUniDataSets.ToJSON(AValue); + try + TJSONValueWriter.WriteJSONValue(LResult, AMediaType, AOutputStream, AActivation); + finally + LResult.Free; + end; +end; + +{ TFDDataSetWriter } + +procedure TUniDataSetWriter.WriteTo(const AValue: TValue; const AMediaType: TMediaType; + AOutputStream: TStream; const AActivation: IMARSActivation); +var + LDataset: TMemDataSet; +begin + LDataset := AValue.AsType; + + if AMediaType.Matches(TMediaType.APPLICATION_XML) then + LDataSet.SaveToXML(AOutputStream) + else if AMediaType.Matches(APPLICATION_JSON_UniDAC) then + TArrayUniDataSetWriter.WriteDataSets( + TValue.From>([LDataSet]) + , AMediaType, AOutputStream, AActivation + ) + else if AMediaType.Matches(TMediaType.APPLICATION_OCTET_STREAM) then + LDataSet.SaveToXML(AOutputStream) + else + raise EMARSException.CreateFmt('Unsupported media type: %s', [AMediaType.ToString]); +end; + +{ TArrayFDMemTableReader } + +function TArrayUniMemTableReader.ReadFrom( +{$ifdef Delphi10Berlin_UP}const AInputData: TBytes;{$else}const AInputData: AnsiString;{$endif} + const ADestination: TRttiObject; const AMediaType: TMediaType; + const AActivation: IMARSActivation +): TValue; +var + LJSON: TJSONObject; +begin + Result := TValue.Empty; + + LJSON := TJSONValueReader.ReadJSONValue(AInputData, ADestination, AMediaType, AActivation).AsType; + if not Assigned(LJSON) then + Exit; + try + Result := TValue.From>(TUniDataSets.FromJSON(LJSON)); + finally + LJSON.Free; + end; +end; + +procedure RegisterReadersAndWriters; +begin + TMARSMessageBodyRegistry.Instance.RegisterWriter(TUniDataSetWriter); + + TMARSMessageBodyRegistry.Instance.RegisterWriter( + TArrayUniDataSetWriter + , function (AType: TRttiType; const AAttributes: TAttributeArray; AMediaType: string): Boolean + begin + Result := Assigned(AType) and AType.IsDynamicArrayOf; + end + , function (AType: TRttiType; const AAttributes: TAttributeArray; AMediaType: string): Integer + begin + Result := TMARSMessageBodyRegistry.AFFINITY_MEDIUM; + end + ); + + TMARSMessageBodyReaderRegistry.Instance.RegisterReader( + TArrayUniMemTableReader + , function(AType: TRttiType; const AAttributes: TAttributeArray; AMediaType: string): Boolean + begin + Result := Assigned(AType) and AType.IsDynamicArrayOf; + end + , function (AType: TRttiType; const AAttributes: TAttributeArray; AMediaType: string): Integer + begin + Result := TMARSMessageBodyRegistry.AFFINITY_MEDIUM + end + ); +end; + +initialization + RegisterReadersAndWriters; + +end. diff --git a/Source/MARS.Data.UniDAC.Utils.pas b/Source/MARS.Data.UniDAC.Utils.pas new file mode 100644 index 00000000..f9d4e749 --- /dev/null +++ b/Source/MARS.Data.UniDAC.Utils.pas @@ -0,0 +1,260 @@ +unit MARS.Data.UniDAC.Utils; + +interface + +uses + Classes, SysUtils, Generics.Collections, Rtti, System.JSON, Data.DB + // Devart UniDAC + , MemDS, VirtualTable + // MARS + , MARS.Core.JSON +; + +const + APPLICATION_JSON_UniDAC = 'application/json-unidac'; + +type + TMARSUniApplyUpdatesRes = record + dataset: string; + result: Integer; + errorCount: Integer; + errors: TArray; + constructor Create(const ADatasetName: string); + procedure AddError(E: EDatabaseError); + procedure Clear; + end; + + + TUniDataSets = class + private + protected + class procedure WriteDataSet(const ADest: TJSONObject; const ADataSet: TMemDataSet; + const ADefaultName: string); + public + // Base64(Zip(binary format)) + class function DataSetToEncodedBinaryString(const ADataSet: TMemDataSet): string; + class procedure EncodedBinaryStringToDataSet(const AString: string; const ADataSet: TVirtualTable); + + class function ToJSON(const ADataSets: TValue): TJSONObject; overload; + class procedure ToJSON(const ADataSets: TValue; const AStream: TStream; + const AEncoding: TEncoding = nil); overload; + + class function ToJSON(const ADataSets: TArray): TJSONObject; overload; + class procedure ToJSON(const ADataSets: TArray; const AStream: TStream; + const AEncoding: TEncoding = nil); overload; + + class function FromJSON(const AJSON: TJSONObject): TArray; overload; + class function FromJSON(const AStream: TStream; const AEncoding: TEncoding = nil): TArray; overload; + + class procedure FreeAll(var ADataSets: TArray); overload; +// class procedure FreeAll(var ADataSets: TArray); overload; + end; + +implementation + +uses + MARS.Core.Utils, MARS.Core.Exceptions +; + +class function TUniDataSets.ToJSON(const ADataSets: TArray): TJSONObject; +var + LIndex: Integer; +begin + Result := TJSONObject.Create; + try + for LIndex := Low(ADataSets) to High(ADataSets) do + WriteDataSet(Result, ADataSets[LIndex], 'DataSet' + LIndex.ToString); + except + Result.Free; + raise; + end; +end; + +class procedure TUniDataSets.FreeAll(var ADataSets: TArray); +var + LDataSet: TMemDataSet; +begin + for LDataSet in ADataSets do + LDataSet.DisposeOf; + ADataSets := []; +end; + +class function TUniDataSets.DataSetToEncodedBinaryString( + const ADataSet: TMemDataSet): string; +var + LBinStream, LZippedStream: TMemoryStream; +begin + Result := ''; + // Get Binary representation + LBinStream := TMemoryStream.Create; + try + ADataSet.SaveToXML(LBinStream); + + // Zip + LZippedStream := TMemoryStream.Create; + try + ZipStream(LBinStream, LZippedStream); + + Result := StreamToBase64(LZippedStream); + finally + LZippedStream.Free; + end; + finally + LBinStream.Free; + end; +end; + +class procedure TUniDataSets.EncodedBinaryStringToDataSet(const AString: string; + const ADataSet: TVirtualTable); +var + LZippedStream, LStream: TMemoryStream; +begin + Assert(Assigned(ADataSet)); + + LZippedStream := TMemoryStream.Create; + try + Base64ToStream(AString, LZippedStream); + LZippedStream.Position := 0; + + LStream := TMemoryStream.Create; + try + UnzipStream(LZippedStream, LStream); + LStream.Position := 0; + + + ADataSet.LoadFromStream(LStream); + finally + LStream.Free; + end; + finally + LZippedStream.Free; + end; +end; + +// not sure if still needed +//class procedure TUniDataSets.FreeAll(var ADataSets: TArray); +//begin +// FreeAll(TArray(ADataSets)); +//end; + +class function TUniDataSets.FromJSON(const AStream: TStream; + const AEncoding: TEncoding): TArray; +var + LJSONObject: TJSONObject; +begin + LJSONObject := StreamToJSONValue(AStream, AEncoding) as TJSONObject; + try + Result := TUniDataSets.FromJSON(LJSONObject); + finally + LJSONObject.Free; + end; +end; + +class function TUniDataSets.ToJSON(const ADataSets: TValue): TJSONObject; +var + LIndex: Integer; +begin + Assert(ADataSets.IsArray); + + Result := TJSONObject.Create; + try + for LIndex := 0 to ADataSets.GetArrayLength-1 do + WriteDataSet(Result, ADataSets.GetArrayElement(LIndex).AsObject as TMemDataSet + , 'DataSet' + LIndex.ToString); + except + Result.Free; + raise; + end; +end; + +class function TUniDataSets.FromJSON(const AJSON: TJSONObject): TArray; +var + LPair: TJSONPair; + LMemTable: TVirtualTable; +begin + Result := []; + for LPair in AJSON do + begin + if not (LPair.JsonValue is TJSONString) then + raise EMARSException.Create('Invalid JSON format [JSONToFDDataSets]'); + + LMemTable := TVirtualTable.Create(nil); + try + EncodedBinaryStringToDataSet((LPair.JsonValue as TJSONString).Value, LMemTable); + LMemTable.Name := LPair.JsonString.Value; + Result := Result + [LMemTable]; + except + LMemTable.Free; + raise; + end; + end; +end; + + +class procedure TUniDataSets.ToJSON(const ADataSets: TArray; + const AStream: TStream; const AEncoding: TEncoding); +var + LJSONObject: TJSONObject; +begin + LJSONObject := TUniDataSets.ToJSON(ADataSets); + try + JSONValueToStream(LJSONObject, AStream, AEncoding); + finally + LJSONObject.Free; + end; +end; + +class procedure TUniDataSets.ToJSON(const ADataSets: TValue; + const AStream: TStream; const AEncoding: TEncoding); +var + LJSONObject: TJSONObject; +begin + LJSONObject := TUniDataSets.ToJSON(ADataSets); + try + JSONValueToStream(LJSONObject, AStream, AEncoding); + finally + LJSONObject.Free; + end; +end; + +class procedure TUniDataSets.WriteDataSet(const ADest: TJSONObject; const ADataSet: TMemDataSet; + const ADefaultName: string); +var + LName: string; +begin + Assert(Assigned(ADest)); + Assert(Assigned(ADataSet)); + + if not ADataSet.Active then + ADataSet.Active := True; + LName := ADataSet.Name; + if LName = '' then + LName := ADefaultName; + + ADest.WriteStringValue(LName, DataSetToEncodedBinaryString(ADataSet)); +end; + +{ TMARSFDApplyUpdatesRes } + +procedure TMARSUniApplyUpdatesRes.AddError(E: EDatabaseError); +begin + errorCount := errorCount + 1; + errors := errors + [E.ClassName + ': ' + E.Message]; +end; + +procedure TMARSUniApplyUpdatesRes.Clear; +begin + dataset := ''; + result := 0; + errorCount := 0; + errors := []; +end; + +constructor TMARSUniApplyUpdatesRes.Create(const ADatasetName: string); +begin + Clear; + dataset := ADatasetName; +end; + + +end. diff --git a/Source/MARS.Data.UniDAC.pas b/Source/MARS.Data.UniDAC.pas new file mode 100644 index 00000000..55ea7ca4 --- /dev/null +++ b/Source/MARS.Data.UniDAC.pas @@ -0,0 +1,721 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) +unit MARS.Data.UniDAC; + +{$I MARS.inc} + +interface + +uses + System.Classes, System.SysUtils, Generics.Collections, Rtti, Data.DB + // Devart UniDAC + , Uni, UniProvider, VirtualTable + // MARS + , MARS.Core.Application + , MARS.Core.Attributes + , MARS.Core.Classes + , MARS.Core.Declarations + , MARS.Core.JSON + , MARS.Core.MediaType + , MARS.Core.Registry + , MARS.Core.Token + , MARS.Core.URL + , MARS.Utils.Parameters + , MARS.Core.Activation.Interfaces + , MARS.Data.UniDAC.Utils +; + +type + EMARSUniDACException = class(EMARSApplicationException); + + MARSUniDACAttribute = class(TCustomAttribute); + + ConnectionAttribute = class(MARSUniDACAttribute) + private + FConnectionDefName: string; + FExpandMacros: Boolean; + public + constructor Create(AConnectionDefName: string; const AExpandMacros: Boolean = False); + property ConnectionDefName: string read FConnectionDefName; + property ExpandMacros: Boolean read FExpandMacros; + end; + + SQLStatementAttribute = class(MARSUniDACAttribute) + private + FName: string; + FSQLStatement: string; + public + constructor Create(AName, ASQLStatement: string); + property Name: string read FName; + property SQLStatement: string read FSQLStatement; + end; + + TContextValueProviderProc = reference to procedure (const AActivation: IMARSActivation; + const AName: string; const ADesiredType: TFieldType; out AValue: TValue); + + TMARSUniMemTable = class(TVirtualTable) + private + FApplyUpdatesRes: TMARSUniApplyUpdatesRes; + protected +// function DoApplyUpdates(ATable: TVirtualTable; AMaxErrors: Integer): Integer; override; +// procedure DoBeforeApplyUpdate; override; +// procedure DoUpdateError(DataSet: TDataSet; E: EDatabaseError; +// UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction); override; + public + property ApplyUpdatesRes: TMARSUniApplyUpdatesRes read FApplyUpdatesRes; + end; + + TMARSUniDAC = class + private + FConnectionDefName: string; + FConnection: TUniConnection; + FActivation: IMARSActivation; + class var FConnectionDefs: TDictionary; + protected + procedure DoUpdateError(DataSet: TDataSet; E: EDatabaseError; + UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction); + procedure SetConnectionDefName(const Value: string); virtual; + function GetConnection: TUniConnection; virtual; + class var FContextValueProviders: TArray; + public + const PARAM_AND_MACRO_DELIMITER = '_'; + + class function GetContextValue(const AName: string; const AActivation: IMARSActivation; + const ADesiredType: TFieldType = ftUnknown): TValue; virtual; + + procedure InjectParamValues(const ACommand: TUniSQL; + const AOnlyIfEmpty: Boolean = True); overload; virtual; + procedure InjectParamValues(const ACommand: TUniQuery; + const AOnlyIfEmpty: Boolean = True); overload; virtual; + procedure InjectMacroValues(const ACommand: TUniSQL; + const AOnlyIfEmpty: Boolean = True); overload; virtual; + procedure InjectMacroValues(const ACommand: TUniQuery; + const AOnlyIfEmpty: Boolean = True); overload; virtual; + + procedure InjectMacroAndParamValues(const ACommand: TUniSQL; const AOnlyIfEmpty: Boolean = True); overload; + procedure InjectMacroAndParamValues(const ACommand: TUniQuery; const AOnlyIfEmpty: Boolean = True); overload; + + constructor Create(const AConnectionDefName: string; + const AActivation: IMARSActivation = nil); virtual; + destructor Destroy; override; + + // Need to understand better before I do coding about below -Ertan +// function ApplyUpdates(const ADelta: TVirtualTable; const AUniQuery: TUniQuery; +// const AMaxErrors: Integer = -1): TMARSUniApplyUpdatesRes; overload; virtual; + + // Need to understand better before I do coding about below -Ertan +// function ApplyUpdates(ADataSets: TArray; ADeltas: TArray; +// AOnApplyUpdates: TProc = nil; +// ): TArray; overload; virtual; + + function CreateCommand(const ASQL: string = ''; const ATransaction: TUniTransaction = nil; + const AContextOwned: Boolean = True): TUniSQL; virtual; + function CreateQuery(const ASQL: string = ''; const ATransaction: TUniTransaction = nil; + const AContextOwned: Boolean = True; const AName: string = 'DataSet'): TUniQuery; virtual; + function CreateTransaction(const AContextOwned: Boolean = True): TUniTransaction; virtual; + + procedure ExecuteSQL(const ASQL: string; const ATransaction: TUniTransaction = nil; + const ABeforeExecute: TProc = nil; + const AAfterExecute: TProc = nil); virtual; + + function Query(const ASQL: string): TUniQuery; overload; virtual; + + function Query(const ASQL: string; + const ATransaction: TUniTransaction): TUniQuery; overload; virtual; + + function Query(const ASQL: string; const ATransaction: TUniTransaction; + const AContextOwned: Boolean): TUniQuery; overload; virtual; + + function Query(const ASQL: string; const ATransaction: TUniTransaction; + const AContextOwned: Boolean; + const AOnBeforeOpen: TProc): TUniQuery; overload; virtual; + + procedure Query(const ASQL: string; const ATransaction: TUniTransaction; + const AOnBeforeOpen: TProc; + const AOnDataSetReady: TProc); overload; virtual; + + function SetName(const AComponent: T; const AName: string): T; overload; + + procedure InTransaction(const ADoSomething: TProc); + + property Connection: TUniConnection read GetConnection; + property ConnectionDefName: string read FConnectionDefName write SetConnectionDefName; + property Activation: IMARSActivation read FActivation; + + class function LoadConnectionDefs(const AParameters: TMARSParameters; + const ASliceName: string = ''): TArray; + class procedure CloseConnectionDefs(const AConnectionDefNames: TArray); + class function CreateConnectionByDefName(const AConnectionDefName: string; + const AActivation: IMARSActivation): TUniConnection; + class function CreateConnectionByConnectString(const AConnectString: string): TUniConnection; + + class constructor CreateClass; + class destructor DestroyClass; + class procedure AddContextValueProvider(const AContextValueProviderProc: TContextValueProviderProc); + end; + +implementation + +uses + StrUtils, Variants, DBAccess + , MARS.Core.Utils + , MARS.Core.Exceptions + , MARS.Data.Utils + , MARS.Rtti.Utils + , MARS.Data.UniDAC.InjectionService + , MARS.Data.UniDAC.ReadersAndWriters +; + +function GetAsTStrings(const AParameters: TMARSParameters): TStrings; +var + LParam: TPair; +begin + Result := TStringList.Create; + try + for LParam in AParameters do + Result.Values[LParam.Key] := LParam.Value.ToString; + except + Result.Free; + raise; + end; +end; + +class function TMARSUniDAC.LoadConnectionDefs(const AParameters: TMARSParameters; + const ASliceName: string = ''): TArray; +var + LData, LConnectionParams: TMARSParameters; + LConnectionDefNames: TArray; + LConnectionDefName: string; + LParams: TStrings; + LConnectString: string; +begin + Result := []; + LData := TMARSParameters.Create(''); + try + LData.CopyFrom(AParameters, ASliceName); + LConnectionDefNames := LData.SliceNames; + + for LConnectionDefName in LConnectionDefNames do + begin + LConnectionParams := TMARSParameters.Create(LConnectionDefName); + try + LConnectionParams.CopyFrom(LData, LConnectionDefName); + LParams := GetAsTStrings(LConnectionParams); + try + LConnectString := LConnectionParams.ByNameText('ConnectString', '').AsString; + if LConnectString = '' then + begin + LParams.Delimiter := ';'; + LParams.QuoteChar := #0; + LConnectString := LParams.DelimitedText; + end; + + FConnectionDefs.AddOrSetValue(LConnectionDefName, LConnectString); + + Result := Result + [LConnectionDefName]; + finally + LParams.Free; + end; + finally + LConnectionParams.Free; + end; + end; + finally + LData.Free; + end; +end; + +function TMARSUniDAC.Query(const ASQL: string; + const ATransaction: TUniTransaction): TUniQuery; +begin + Result := Query(ASQL, ATransaction, True); +end; + +function TMARSUniDAC.Query(const ASQL: string; + const ATransaction: TUniTransaction; const AContextOwned: Boolean; + const AOnBeforeOpen: TProc): TUniQuery; +begin + Result := CreateQuery(ASQL, ATransaction, AContextOwned); + try + if Assigned(AOnBeforeOpen) then + AOnBeforeOpen(Result); + Result.Open; + except + if not AContextOwned then + Result.Free; + raise; + end; +end; + +function TMARSUniDAC.Query(const ASQL: string): TUniQuery; +begin + Result := Query(ASQL, nil, True); +end; + +procedure TMARSUniDAC.Query(const ASQL: string; const ATransaction: TUniTransaction; + const AOnBeforeOpen, AOnDataSetReady: TProc); +var + LQuery: TUniQuery; +begin + LQuery := Query(ASQL, ATransaction, False, AOnBeforeOpen); + try + if Assigned(AOnDataSetReady) then + AOnDataSetReady(LQuery); + finally + LQuery.Free; + end; +end; + +function TMARSUniDAC.Query(const ASQL: string; + const ATransaction: TUniTransaction; const AContextOwned: Boolean): TUniQuery; +begin + Result := CreateQuery(ASQL, ATransaction, AContextOwned); + Result.Open; +end; + +class function TMARSUniDAC.CreateConnectionByConnectString(const AConnectString: string): TUniConnection; +begin + Result := TUniConnection.Create(nil); + try + Result.ConnectString := AConnectString; + except + Result.Free(); + raise; + end; +end; + +class function TMARSUniDAC.CreateConnectionByDefName( + const AConnectionDefName: string; const AActivation: IMARSActivation): TUniConnection; +var + LConnectString: string; +begin + //AM TDictionary is not thread-safe but connection definitions are not supposed + // to change during server execution. A monitor object would be a safer choice, if + // errors should arise. + Result := nil; + if FConnectionDefs.TryGetValue(AConnectionDefName, LConnectString) then + Result := CreateConnectionByConnectString(LConnectString) +end; + +{ ConnectionAttribute } + +constructor ConnectionAttribute.Create(AConnectionDefName: string; const AExpandMacros: Boolean = False); +begin + inherited Create; + FConnectionDefName := AConnectionDefName; + FExpandMacros := AExpandMacros; +end; + +{ SQLStatementAttribute } + +constructor SQLStatementAttribute.Create(AName, ASQLStatement: string); +begin + inherited Create; + FName := AName; + FSQLStatement := ASQLStatement; +end; + +{ TMARSUniDAC } + +class procedure TMARSUniDAC.AddContextValueProvider( + const AContextValueProviderProc: TContextValueProviderProc); +begin + FContextValueProviders := FContextValueProviders + [TContextValueProviderProc(AContextValueProviderProc)]; +end; + +function DataSetByName(const ADataSets: TArray; const AName: string): TVirtualTable; +var + LCurrent: TVirtualTable; +begin + Result := nil; + for LCurrent in ADataSets do + if SameText(LCurrent.Name, AName) then + begin + Result := LCurrent; + Break; + end; + if Result = nil then + raise EMARSUniDACException.CreateFmt('DataSet %s not found', [AName]); +end; + +// Need to understand ApplyUpdates in MARS better before trying to convert below code lines -Ertan +(* +function TMARSUniDAC.ApplyUpdates(const ADelta: TMARSUniMemTable; + const AUniQuery: TUniQuery; + const AMaxErrors: Integer): TMARSUniApplyUpdatesRes; +var + LUniMemTable: TMARSUniMemTable; + LUniQuery: TUniQuery; +begin + Assert(AUniQuery <> nil); + + Result.Clear; + + LUniMemTable := TMARSUniMemTable.Create(nil); + try + LUniMemTable.Name := AUniQuery.Name; + LUniQuery := TUniQuery.Create(nil); + try + if Assigned(AUniQuery.SQL) then + LUniQuery.SQL := AUniQuery.SQL; + if Assigned(AUniQuery.SQLInsert) then + LUniQuery.SQLInsert := AUniQuery.SQLInsert; + if Assigned(AUniQuery.SQLUpdate) then + LUniQuery.SQLUpdate := AUniQuery.SQLUpdate; + if Assigned(AUniQuery.SQLDelete) then + LUniQuery.SQLDelete := AUniQuery.SQLDelete; + + LUniQuery.UpdatingTable := AUniQuery.UpdatingTable; + if LUniQuery.UpdatingTable = '' then + LUniQuery.UpdatingTable := LUniQuery.SQL.UpdateOptions.UpdateTableName; + + LUniMemTable.ResourceOptions.StoreItems := [siMeta, siDelta]; + LUniMemTable.CachedUpdates := True; + LUniMemTable.Adapter := LUniQuery; + LUniMemTable.Data := ADelta.Data; + LUniMemTable.ApplyUpdates(AMaxErrors); + Result := LUniMemTable.ApplyUpdatesRes; + finally + LUniQuery.Free; + end; + finally + LUniMemTable.Free; + end; +end; + +function TMARSUniDAC.ApplyUpdates(ADataSets: TArray; + ADeltas: TArray; + AOnBeforeApplyUpdates: TProc): TArray; +var + LDelta: TVirtualTable; + LDataSet: TFDAdaptedDataSet; + LFDAdapter: TFDTableAdapter; + +begin + Result := []; + for LDelta in ADeltas do + begin + LDataSet := DataSetByName(ADataSets, LDelta.Name) as TFDAdaptedDataSet; + Assert(LDataSet <> nil); + Assert(LDataSet.Command <> nil); + + InjectMacroAndParamValues(LDataSet.Command); + + if Assigned(AOnBeforeApplyUpdates) then + AOnBeforeApplyUpdates(LDataSet, LDelta); + + LFDAdapter := TFDTableAdapter.Create(nil); + try + LFDAdapter.Name := LDataSet.Name; + LFDAdapter.SelectCommand := LDataSet.Command; + Result := Result + [ApplyUpdates(LDelta, LFDAdapter, -1)]; + finally + LFDAdapter.Free; + end; + end; +end; +*) + +class procedure TMARSUniDAC.CloseConnectionDefs( + const AConnectionDefNames: TArray); +begin + FConnectionDefs.Clear; +end; + +constructor TMARSUniDAC.Create(const AConnectionDefName: string; + const AActivation: IMARSActivation); +begin + inherited Create(); + ConnectionDefName := AConnectionDefName; + FActivation := AActivation; +end; + +class constructor TMARSUniDAC.CreateClass; +begin + FContextValueProviders := []; + FConnectionDefs := TDictionary.Create(); +end; + +function TMARSUniDAC.CreateCommand(const ASQL: string; + const ATransaction: TUniTransaction; const AContextOwned: Boolean): TUniSQL; +begin + Result := TUniSQL.Create(nil); + try + Result.Connection := Connection; + Result.Transaction := ATransaction; + Result.SQL.Text := ASQL; + InjectMacroAndParamValues(Result); + if AContextOwned then + Activation.AddToContext(Result); + except + Result.Free; + raise; + end; +end; + +function TMARSUniDAC.CreateQuery(const ASQL: string; const ATransaction: TUniTransaction; + const AContextOwned: Boolean; const AName: string): TUniQuery; +begin + Result := TUniQuery.Create(nil); + try + Result.Name := AName; + Result.Connection := Connection; + Result.Transaction := ATransaction; + Result.SQL.Text := ASQL; + InjectMacroAndParamValues(Result); + if AContextOwned then + Activation.AddToContext(Result); + except + Result.Free; + raise; + end; +end; + +function TMARSUniDAC.CreateTransaction(const AContextOwned: Boolean): TUniTransaction; +begin + Result := TUniTransaction.Create(nil); + try + Result.DefaultConnection := Connection; + if AContextOwned then + Activation.AddToContext(Result); + except + Result.Free; + raise; + end; +end; + +destructor TMARSUniDAC.Destroy; +begin + FreeAndNil(FConnection); + inherited; +end; + +class destructor TMARSUniDAC.DestroyClass; +begin + FreeAndNil(FConnectionDefs); +end; + +procedure TMARSUniDAC.DoUpdateError(DataSet: TDataSet; E: EDatabaseError; + UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction); +begin + +end; + +procedure TMARSUniDAC.ExecuteSQL(const ASQL: string; const ATransaction: TUniTransaction; + const ABeforeExecute, AAfterExecute: TProc); +var + LCommand: TUniSQL; +begin + LCommand := CreateCommand(ASQL, ATransaction, False); + try + if Assigned(ABeforeExecute) then + ABeforeExecute(LCommand); + LCommand.Execute(); + if Assigned(AAfterExecute) then + AAfterExecute(LCommand); + finally + LCommand.Free; + end; +end; + +function TMARSUniDAC.GetConnection: TUniConnection; +begin + if not Assigned(FConnection) then + FConnection := CreateConnectionByDefName(ConnectionDefName, Activation); + Result := FConnection; +end; + +class function TMARSUniDAC.GetContextValue(const AName: string; const AActivation: IMARSActivation; + const ADesiredType: TFieldType): TValue; +var + LFirstToken, LSecondToken: string; + LHasThirdToken: Boolean; + LSecondTokenAndAll, LThirdTokenAndAll: string; + LNameTokens: TArray; + LFirstDelim, LSecondDelim: Integer; + + LIndex: Integer; + LCustomProvider: TContextValueProviderProc; +begin + Result := TValue.Empty; + LNameTokens := AName.Split([PARAM_AND_MACRO_DELIMITER]); + if Length(LNameTokens) < 2 then + Exit; + + LFirstToken := LNameTokens[0]; + LSecondToken := LNameTokens[1]; + LFirstDelim := AName.IndexOf(PARAM_AND_MACRO_DELIMITER); + LSecondTokenAndAll := AName.Substring(LFirstDelim + 1); + LHasThirdToken := Length(LNameTokens) > 2; + if LHasThirdToken then + begin + LSecondDelim := AName.IndexOf(PARAM_AND_MACRO_DELIMITER, LFirstDelim + Length(PARAM_AND_MACRO_DELIMITER)); + LThirdTokenAndAll := AName.Substring(LSecondDelim + 1); + end; + + if SameText(LFirstToken, 'Token') then + begin + Result := ReadPropertyValue(AActivation.Token, LSecondToken); + + if SameText(LSecondToken, 'HasRole') and LHasThirdToken then + Result := AActivation.Token.HasRole(LThirdTokenAndAll) + else if SameText(LSecondToken, 'Claim') and LHasThirdToken then + Result := AActivation.Token.Claims.ByNameText(LThirdTokenAndAll); + end + else if SameText(LFirstToken, 'PathParam') then + begin + LIndex := AActivation.URLPrototype.GetPathParamIndex(LSecondTokenAndAll); + if (LIndex > -1) and (LIndex < Length(AActivation.URL.PathTokens)) then + Result := AActivation.URL.PathTokens[LIndex] { TODO -oAndrea : Try to convert according to ADesiredType } + else + raise EMARSUniDACException.CreateFmt('PathParam not found: %s', [LSecondTokenAndAll]); + end + else if SameText(LFirstToken, 'QueryParam') then + Result := AActivation.URL.QueryTokenByName(LSecondTokenAndAll) + else if SameText(LFirstToken, 'FormParam') then + Result := AActivation.Request.GetFormParamValue(LSecondTokenAndAll) + else if SameText(LFirstToken, 'Request') then + Result := ReadPropertyValue(AActivation.Request.AsObject, LSecondTokenAndAll) +// else if SameText(LFirstToken, 'Response') then +// Result := ReadPropertyValue(AActivation.Response, LSecondToken) + else if SameText(LFirstToken, 'URL') then + Result := ReadPropertyValue(AActivation.URL, LSecondTokenAndAll) + else if SameText(LFirstToken, 'URLPrototype') then + Result := ReadPropertyValue(AActivation.URLPrototype, LSecondTokenAndAll) + else // last chance, custom injection + for LCustomProvider in FContextValueProviders do + LCustomProvider(AActivation, AName, ADesiredType, Result); +end; + +procedure TMARSUniDAC.InjectMacroAndParamValues( + const ACommand: TUniSQL; const AOnlyIfEmpty: Boolean); +begin + InjectMacroValues(ACommand, AOnlyIfEmpty); + InjectParamValues(ACommand, AOnlyIfEmpty); +end; + +procedure TMARSUniDAC.InjectMacroAndParamValues( + const ACommand: TUniQuery; const AOnlyIfEmpty: Boolean); +begin + InjectMacroValues(ACommand, AOnlyIfEmpty); + InjectParamValues(ACommand, AOnlyIfEmpty); +end; + +procedure TMARSUniDAC.InjectMacroValues(const ACommand: TUniSQL; const AOnlyIfEmpty: Boolean); +var + LIndex: Integer; + LMacro: TMacro; +begin + for LIndex := 0 to ACommand.Macros.Count-1 do + begin + LMacro := ACommand.Macros[LIndex]; + if (not AOnlyIfEmpty) or (LMacro.Value = '') then + LMacro.Value := GetContextValue(LMacro.Name, Activation).AsVariant; + end; +end; + +procedure TMARSUniDAC.InjectMacroValues(const ACommand: TUniQuery; const AOnlyIfEmpty: Boolean); +var + LIndex: Integer; + LMacro: TMacro; +begin + for LIndex := 0 to ACommand.Macros.Count-1 do + begin + LMacro := ACommand.Macros[LIndex]; + if (not AOnlyIfEmpty) or (LMacro.Value = '') then + LMacro.Value := GetContextValue(LMacro.Name, Activation).AsVariant; + end; +end; + +procedure TMARSUniDAC.InjectParamValues(const ACommand: TUniSQL; const AOnlyIfEmpty: Boolean); +var + LIndex: Integer; + LParam: TUniParam; +begin + for LIndex := 0 to ACommand.Params.Count-1 do + begin + LParam := ACommand.Params[LIndex]; + if ((not AOnlyIfEmpty) or LParam.IsNull) then + LParam.Value := GetContextValue(LParam.Name, Activation, LParam.DataType).AsVariant; + end; +end; + +procedure TMARSUniDAC.InjectParamValues(const ACommand: TUniQuery; const AOnlyIfEmpty: Boolean); +var + LIndex: Integer; + LParam: TUniParam; +begin + for LIndex := 0 to ACommand.Params.Count-1 do + begin + LParam := ACommand.Params[LIndex]; + if ((not AOnlyIfEmpty) or LParam.IsNull) then + LParam.Value := GetContextValue(LParam.Name, Activation, LParam.DataType).AsVariant; + end; +end; + +procedure TMARSUniDAC.InTransaction(const ADoSomething: TProc); +var + LTransaction: TUniTransaction; +begin + if Assigned(ADoSomething) then + begin + LTransaction := CreateTransaction(False); + try + LTransaction.StartTransaction; + try + ADoSomething(LTransaction); + LTransaction.Commit; + except + LTransaction.Rollback; + raise; + end; + finally + LTransaction.Free; + end; + end; +end; + +procedure TMARSUniDAC.SetConnectionDefName(const Value: string); +begin + if FConnectionDefName <> Value then + begin + FreeAndNil(FConnection); + FConnectionDefName := Value; + end; +end; + +function TMARSUniDAC.SetName(const AComponent: T; const AName: string): T; +begin + AComponent.Name := AName; + Result := AComponent; +end; + +{ TMARSFDMemTable } + +// Need to understand better before doing anything below -Ertan +(* +function TMARSFDMemTable.DoApplyUpdates(ATable: TFDDatSTable; + AMaxErrors: Integer): Integer; +begin + Result := inherited DoApplyUpdates(ATable, AMaxErrors); + FApplyUpdatesRes.result := Result; +end; + +procedure TMARSFDMemTable.DoBeforeApplyUpdate; +begin + inherited; + FApplyUpdatesRes := TMARSUniApplyUpdatesRes.Create(Self.Name); +end; + +procedure TMARSUniMemTable.DoUpdateErrorHandler(DataSet: TDataSet; E: EDatabaseError; + UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction); +begin + inherited; + FApplyUpdatesRes.AddError(ARow, AException, ARequest); +end; +*) + +end. diff --git a/Source/MARS.Data.Utils.pas b/Source/MARS.Data.Utils.pas index f7a4953a..95aa21b8 100644 --- a/Source/MARS.Data.Utils.pas +++ b/Source/MARS.Data.Utils.pas @@ -28,10 +28,8 @@ function DatasetMetadataToJSONObject(const ADataSet: TDataSet): TJSONObject; implementation uses - Rtti - , StrUtils, DateUtils - , MARS.Rtti.Utils - , MARS.Core.Utils; + Rtti, StrUtils, DateUtils, System.JSON +, MARS.Rtti.Utils, MARS.Core.Utils; type TJSONFieldType = (NestedObject, NestedArray, SimpleValue); @@ -260,6 +258,8 @@ function DataSetToXML(const ADataSet: TDataSet; const AAcceptFunc: TFunc'; end; function DatasetMetadataToJSONObject(const ADataSet: TDataSet): TJSONObject; diff --git a/Source/MARS.JOSEJWT.Token.pas b/Source/MARS.JOSEJWT.Token.pas index 77506dfb..12a47542 100644 --- a/Source/MARS.JOSEJWT.Token.pas +++ b/Source/MARS.JOSEJWT.Token.pas @@ -46,7 +46,7 @@ function TMARSJOSEJWTToken.BuildJWTToken(const ASecret: string; try LKey := TJWK.Create(ASecret); try - LSigner.Sign(LKey, HS256); + LSigner.Sign(LKey, TJOSEAlgorithmId.HS256); Result := LSigner.CompactToken; finally diff --git a/Source/MARS.JsonDataObjects.ReadersAndWriters.pas b/Source/MARS.JsonDataObjects.ReadersAndWriters.pas index 5b11e3db..b4b2bffb 100644 --- a/Source/MARS.JsonDataObjects.ReadersAndWriters.pas +++ b/Source/MARS.JsonDataObjects.ReadersAndWriters.pas @@ -50,7 +50,7 @@ procedure TJsonDataObjectsWriter.WriteTo(const AValue: TValue; const AMediaType: LJsonBO: TJsonBaseObject; LEncoding: TEncoding; begin - if not GetDesiredEncoding(AActivation, LEncoding) then + if not TMARSMessageBodyWriter.GetDesiredEncoding(AActivation, LEncoding) then LEncoding := TEncoding.UTF8; LJsonBO := AValue.AsObject as TJsonBaseObject; @@ -87,5 +87,3 @@ initialization RegisterReadersAndWriters; end. - - diff --git a/Source/MARS.Rtti.Utils.pas b/Source/MARS.Rtti.Utils.pas index fb4559d3..a087ba00 100644 --- a/Source/MARS.Rtti.Utils.pas +++ b/Source/MARS.Rtti.Utils.pas @@ -1,1058 +1,1077 @@ -(* - Copyright 2016, MARS-Curiosity library - - Home: https://github.com/andrea-magni/MARS -*) -unit MARS.Rtti.Utils; - -{$I MARS.inc} - -interface - -uses - SysUtils, Classes, RTTI, TypInfo - , MARS.Core.JSON - , DB; - -type - TRttiObjectHelper = class helper for TRttiObject - public - function GetRttiType: TRttiType; - procedure SetValue(AInstance: Pointer; const AValue: TValue); - - function HasAttribute(const AInherited: Boolean = False): Boolean; overload; inline; - function HasAttribute( - const ADoSomething: TProc; const AInherited: Boolean = False): Boolean; overload; inline; - - function GetAllAttributes(const AInherited: Boolean = False): TArray; inline; - - function ForEachAttribute( - const ADoSomething: TProc; const AInherited: Boolean = False): Integer; overload; inline; - function ForEachAttribute( - const ADoSomething: TFunc; const AInherited: Boolean = False): Integer; overload; inline; - end; - - TRttiTypeHelper = class helper(TRttiObjectHelper) for TRttiType - protected - public - function ForEachMethodWithAttribute( - const ADoSomething: TFunc): Integer; inline; - - function ForEachFieldWithAttribute( - const ADoSomething: TFunc): Integer; - - function ForEachPropertyWithAttribute( - const ADoSomething: TFunc): Integer; - - function IsDynamicArrayOf(const AAllowInherithance: Boolean = True): Boolean; - function IsDynamicArrayOfRecord: Boolean; - function IsArray: Boolean; overload; - function IsArray(out AElementType: TRttiType): Boolean; overload; - function GetArrayElementType: TRttiType; - - function IsObjectOfType(const AAllowInherithance: Boolean = True): Boolean; overload; - function IsObjectOfType(const AClass: TClass; const AAllowInherithance: Boolean = True): Boolean; overload; - - function FindMethodFunc(const AName: string): TRttiMethod; overload; - function FindMethodFunc(const AName: string): TRttiMethod; overload; - function FindMethodFunc(const AName: string): TRttiMethod; overload; - function FindMethodFunc(const AName: string): TRttiMethod; overload; - - function FindMethodFunc(const AName: string): TRttiMethod; overload; - end; - - TRttiHelper = class - public - class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; - class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; - class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; - class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; - class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; - - class function IfHasAttribute(AInstance: TObject): Boolean; overload; - class function IfHasAttribute(AInstance: TObject; const ADoSomething: TProc): Boolean; overload; - - class function ForEachAttribute(const AInstance: TObject; - const ADoSomething: TProc): Integer; overload; - - class function ForEachAttribute(const AAttributes: TArray; - const ADoSomething: TProc): Integer; overload; - class function ForEachAttribute(const AAttributes: TArray; - const ADoSomething: TFunc): Integer; overload; - - class function ForEachMethodWithAttribute(const AMethods: TArray; - const ADoSomething: TFunc): Integer; - - class function ForEachFieldWithAttribute(AInstance: TObject; const ADoSomething: TFunc): Integer; overload; - class function ForEachField(AInstance: TObject; const ADoSomething: TFunc): Integer; - - class function FindParameterLessConstructor(const AClass: TClass): TRttiMethod; - end; - - TRecord = class - public - class procedure ToDataSet(const ARecord: R; const ADataSet: TDataSet; const AAppend: Boolean = False); - class procedure FromDataSet(var ARecord: R; const ADataSet: TDataSet); - class function DataSetToArray(const ADataSet: TDataSet): TArray; - class procedure SetFieldByName(var ARecord: R; const AFieldName: string; const AValue: TValue); - class function GetFieldByName(var ARecord: R; const AFieldName: string): TValue; overload; - class function GetFieldByName(var ARecord: R; const AFieldName: string; const ADefault: TValue): TValue; overload; - class function FromStrings(const AStrings: TStrings): R; - class procedure ToStrings(const ARecord: R; var AStrings: TStrings; const AClear: Boolean = True); - class function ToArrayOfString(const ARecord: R): TArray; - end; - TOnGetRecordFieldValueProc = reference to procedure (const AName: string; const AField: TRttiField; var AValue: TValue); - - function StringsToRecord(const AStrings: TStrings; const ARecordType: TRttiType; - const AOnGetFieldValue: TOnGetRecordFieldValueProc = nil): TValue; - -function ExecuteMethod(const AInstance: TValue; const AMethodName: string; const AArguments: array of TValue; - const ABeforeExecuteProc: TProc{ = nil}; const AAfterExecuteProc: TProc{ = nil}): Boolean; overload; - -function ExecuteMethod(const AInstance: TValue; AMethod: TRttiMethod; const AArguments: array of TValue; - const ABeforeExecuteProc: TProc{ = nil}; const AAfterExecuteProc: TProc{ = nil}): Boolean; overload; - -function ReadPropertyValue(AInstance: TObject; const APropertyName: string): TValue; -procedure SetArrayLength(var AArray: TValue; const AArrayType: TRttiType; const ANewSize: Integer); - -function StringToTValue(const AString: string; const ADesiredType: TRttiType): TValue; - -implementation - -uses - StrUtils, DateUtils, Generics.Collections - , MARS.Core.Utils -; - -function StringsToRecord(const AStrings: TStrings; const ARecordType: TRttiType; - const AOnGetFieldValue: TOnGetRecordFieldValueProc = nil): TValue; -var - LField: TRttiField; -// LValue: TValue; - LRecordInstance: Pointer; -// LFilterProc: TToRecordFilterProc; - LAccept: Boolean; - LJSONName: string; - LAssignedValuesField: TRttiField; - LAssignedValues: TArray; - LValue: TValue; - -// function GetRecordFilterProc: TToRecordFilterProc; -// var -// LMethod: TRttiMethod; -// begin -// Result := nil; -// // looking for TMyRecord.ToRecordFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; -// -// LMethod := ARecordType.FindMethodFunc('ToRecordFilter'); -// if Assigned(LMethod) then -// Result := -// procedure (const AField: TRttiField; const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean) -// begin -// AAccept := LMethod.Invoke(ARecord, [AField, AJSONObject]).AsBoolean; -// end; -// end; - -begin - TValue.Make(nil, ARecordType.Handle, Result); - LRecordInstance := Result.GetReferenceToRawData; - -// LFilterProc := AFilterProc; -// if not Assigned(LFilterProc) then -// LFilterProc := GetRecordFilterProc(); - - LAssignedValuesField := ARecordType.GetField('_AssignedValues'); - if Assigned(LAssignedValuesField) - and not LAssignedValuesField.FieldType.IsDynamicArrayOf - then - LAssignedValuesField := nil; - LAssignedValues := []; - - for LField in ARecordType.GetFields do - begin - LAccept := True; -// if Assigned(LFilterProc) then -// LFilterProc(LField, Result, Self, LAccept); - - if LAccept then - begin - LJSONName := LField.Name; - LField.HasAttribute( - procedure (AAttr: JSONNameAttribute) - begin - LJSONName := AAttr.Name; - end - ); - if LJSONName <> '' then - begin - LValue := StringToTValue(AStrings.Values[LJSONName], LField.FieldType); - if Assigned(AOnGetFieldValue) then - AOnGetFieldValue(LJSONName, LField, LValue); - LField.SetValue(LRecordInstance, LValue); - LAssignedValues := LAssignedValues + [LField.Name]; - end; - end; - end; - if Assigned(LAssignedValuesField) then - LAssignedValuesField.SetValue(LRecordInstance, TValue.From>(LAssignedValues)); -end; - -function StringToTValue(const AString: string; const ADesiredType: TRttiType): TValue; -begin - if ADesiredType.IsObjectOfType then - Result := TJSONObject.ParseJSONValue(AString) - else - begin - case ADesiredType.TypeKind of - tkInt64: Result := StrToInt64Def(AString, 0); - tkInteger: Result := StrToIntDef(AString, 0); - tkEnumeration: begin - if SameText(ADesiredType.Name, 'Boolean') then - Result := StrToBoolDef(AString, False); - end; - tkFloat: begin - if IndexStr(ADesiredType.Name, ['TDate', 'TDateTime', 'TTime']) <> -1 then - begin - try - Result := ISO8601ToDate(AString); - except - Result := StrToDateTime(AString) - end; - end - else - begin - Result := StrToFloatDef(AString - , StrToFloatDef(AString, 0.0, TFormatSettings.Create('en')) // second chance - ); - end; - end; - - {$ifdef DelphiXE7_UP} - tkChar: begin - if AString.IsEmpty then - Result := '' - else - Result := TValue.From(AString.Chars[0]); - end; - {$else} - tkChar: Result := TValue.From(Copy(AString, 1, 1)); - {$endif} - else - Result := AString; - end; - end; -end; - - -procedure SetArrayLength(var AArray: TValue; const AArrayType: TRttiType; const ANewSize: Integer); -begin - if AArrayType is TRttiArrayType then - begin - raise Exception.Create('Not yet implemented: SetArrayLength TRttiArrayType'); - { TODO -oAndrea : probably not needed } - end - else if AArrayType is TRttiDynamicArrayType then - DynArraySetLength(PPointer(AArray.GetReferenceToRawData)^, AArrayType.Handle, 1, @ANewSize); -end; - -function ReadPropertyValue(AInstance: TObject; const APropertyName: string): TValue; -var - LContext: TRttiContext; - LType: TRttiType; - LProperty: TRttiProperty; -begin - Result := TValue.Empty; - LType := LContext.GetType(AInstance.ClassType); - if Assigned(LType) then - begin - LProperty := LType.GetProperty(APropertyName); - if Assigned(LProperty) then - Result := LProperty.GetValue(AInstance); - end; -end; - -function ExecuteMethod(const AInstance: TValue; AMethod: TRttiMethod; - const AArguments: array of TValue; const ABeforeExecuteProc: TProc{ = nil}; - const AAfterExecuteProc: TProc{ = nil}): Boolean; -var - LResult: TValue; -begin - if Assigned(ABeforeExecuteProc) then - ABeforeExecuteProc(); - LResult := AMethod.Invoke(AInstance, AArguments); - Result := True; - if Assigned(AAfterExecuteProc) then - AAfterExecuteProc(LResult); -end; - -function ExecuteMethod(const AInstance: TValue; const AMethodName: string; - const AArguments: array of TValue; const ABeforeExecuteProc: TProc{ = nil}; - const AAfterExecuteProc: TProc{ = nil}): Boolean; -var - LContext: TRttiContext; - LType: TRttiType; - LMethod: TRttiMethod; -begin - Result := False; - LType := LContext.GetType(AInstance.TypeInfo); - if Assigned(LType) then - begin - LMethod := LType.GetMethod(AMethodName); - if Assigned(LMethod) then - Result := ExecuteMethod(AInstance, LMethod, AArguments, ABeforeExecuteProc, AAfterExecuteProc); - end; -end; - -{ TRttiObjectHelper } - -function TRttiObjectHelper.ForEachAttribute( - const ADoSomething: TProc; const AInherited: Boolean): Integer; -begin - Result := TRttiHelper.ForEachAttribute(GetAllAttributes(AInherited), ADoSomething); -end; - -function TRttiObjectHelper.HasAttribute(const AInherited: Boolean): Boolean; -begin - Result := HasAttribute(nil, AInherited); -end; - -function TRttiObjectHelper.ForEachAttribute( - const ADoSomething: TFunc; const AInherited: Boolean): Integer; -begin - Result := TRttiHelper.ForEachAttribute(GetAllAttributes(AInherited), ADoSomething); -end; - -function TRttiObjectHelper.GetAllAttributes( - const AInherited: Boolean): TArray; -var - LBaseType: TRttiType; - LType: TRttiType; -begin - Result := Self.GetAttributes; - - { TODO -oAndrea : Implement AInherited = True behavior when Self is a TRttiMethod instance } - LType := Self.GetRttiType; - if AInherited and Assigned(LType) then - begin - LBaseType := LType.BaseType; - while Assigned(LBaseType) do - begin - Result := Result + LBaseType.GetAttributes; - LBaseType := LBaseType.BaseType; - end; - end; -end; - -function TRttiObjectHelper.GetRttiType: TRttiType; -begin - Result := nil; - if Self is TRttiField then - Result := TRttiField(Self).FieldType - else if Self is TRttiProperty then - Result := TRttiProperty(Self).PropertyType - else if Self is TRttiParameter then - Result := TRttiParameter(Self).ParamType - else if Self is TRttiType then - Result := TRttiType(Self); -end; - -function TRttiObjectHelper.HasAttribute( - const ADoSomething: TProc; const AInherited: Boolean): Boolean; -var - LAttribute: TCustomAttribute; - LResult: Boolean; -begin - LResult := False; - - ForEachAttribute( - function (AAttr: T): Boolean - begin - Result := False; - LResult := True; - if Assigned(ADoSomething) then - ADoSomething(AAttr); - end - , AInherited - ); - - Result := LResult; -end; - -procedure TRttiObjectHelper.SetValue(AInstance: Pointer; const AValue: TValue); -begin - if Self is TRttiField then - TRttiField(Self).SetValue(AInstance, AValue) - else if Self is TRttiProperty then - TRttiProperty(Self).SetValue(AInstance, AValue) - else if Self is TRttiParameter then - raise ENotSupportedException.Create('Setting value of TRttiParameter not supported'); -end; - -{ TRttiTypeHelper } - -function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; -var - LMethod: TRttiMethod; - LParameters: TArray; -begin - LMethod := GetMethod(AName); - if Assigned(LMethod) then - begin - if not ( - (LMethod.ReturnType.Handle = TypeInfo(R)) - and TRttiHelper.MethodParametersMatch(LMethod) - ) then - LMethod := nil; - end; - - Result := LMethod; -end; - -function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; -var - LMethod: TRttiMethod; - LParameters: TArray; -begin - LMethod := GetMethod(AName); - if Assigned(LMethod) then - begin - if not ( - (LMethod.ReturnType.Handle = TypeInfo(R)) - and TRttiHelper.MethodParametersMatch(LMethod) - ) then - LMethod := nil; - end; - - Result := LMethod; -end; - -function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; -var - LMethod: TRttiMethod; - LParameters: TArray; -begin - LMethod := GetMethod(AName); - if Assigned(LMethod) then - begin - if not ( - (LMethod.ReturnType.Handle = TypeInfo(R)) - and TRttiHelper.MethodParametersMatch(LMethod) - ) then - LMethod := nil; - end; - - Result := LMethod; -end; - -function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; -var - LMethod: TRttiMethod; - LParameters: TArray; -begin - LMethod := GetMethod(AName); - if Assigned(LMethod) then - begin - if not ( - (LMethod.ReturnType.Handle = TypeInfo(R)) - and TRttiHelper.MethodParametersMatch(LMethod) - ) then - LMethod := nil; - end; - - Result := LMethod; -end; - -function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; -var - LMethod: TRttiMethod; - LParameters: TArray; -begin - LMethod := GetMethod(AName); - if Assigned(LMethod) then - begin - if not ( - (LMethod.ReturnType.Handle = TypeInfo(R)) - and TRttiHelper.MethodParametersMatch(LMethod) - ) then - LMethod := nil; - end; - - Result := LMethod; -end; - -function TRttiTypeHelper.ForEachFieldWithAttribute( - const ADoSomething: TFunc): Integer; -var - LField: TRttiField; - LBreak: Boolean; -begin - for LField in Self.GetFields do - begin - LBreak := False; - if LField.HasAttribute( - procedure (AAttrib: T) - begin - if Assigned(ADoSomething) then - begin - if not ADoSomething(LField, AAttrib) then - LBreak := True; - end; - end - ) - then - Inc(Result); - - if LBreak then - Break; - end; -end; - -function TRttiTypeHelper.ForEachMethodWithAttribute( - const ADoSomething: TFunc): Integer; -begin - Result := TRttiHelper.ForEachMethodWithAttribute(Self.GetMethods, ADoSomething); -end; - -function TRttiTypeHelper.ForEachPropertyWithAttribute( - const ADoSomething: TFunc): Integer; -var - LProperty: TRttiProperty; - LBreak: Boolean; -begin - Result := 0; - for LProperty in Self.GetProperties do - begin - LBreak := False; - if LProperty.HasAttribute( - procedure (AAttrib: T) - begin - if Assigned(ADoSomething) then - begin - if not ADoSomething(LProperty, AAttrib) then - LBreak := True; - end; - end - ) - then - Inc(Result); - - if LBreak then - Break; - end; -end; - -function TRttiTypeHelper.GetArrayElementType: TRttiType; -begin - Result := nil; - if Self is TRttiDynamicArrayType then - Result := TRttiDynamicArrayType(Self).ElementType; -end; - -function TRttiTypeHelper.IsArray: Boolean; -begin - Result := (Self is TRttiArrayType) or (Self is TRttiDynamicArrayType); -end; - -function TRttiTypeHelper.IsArray(out AElementType: TRttiType): Boolean; -begin - Result := False; - if (Self is TRttiDynamicArrayType) then - begin - AElementType := TRttiDynamicArrayType(Self).ElementType; - Result := True; - end - else if (Self is TRttiArrayType) then - begin - AElementType := TRttiArrayType(Self).ElementType; - Result := True; - end; -end; - -function TRttiTypeHelper.IsDynamicArrayOf( - const AAllowInherithance: Boolean): Boolean; -var - LElementType: TRttiType; - LType: TRttiType; -begin - Result := False; - if Self is TRttiDynamicArrayType then - begin - LType := TRttiContext.Create.GetType(TypeInfo(T)); - LElementType := TRttiDynamicArrayType(Self).ElementType; - - Result := (LElementType = LType) // exact match - or ( // classes with inheritance check (wrt AAllowInheritance argument) - (LElementType.IsInstance and LType.IsInstance) - and LElementType.IsObjectOfType(TRttiInstanceType(LType).MetaclassType, AAllowInherithance) - ); - end; -end; - -function TRttiTypeHelper.IsDynamicArrayOfRecord: Boolean; -var - LElementType: TRttiType; -begin - Result := False; - if Self is TRttiDynamicArrayType then - begin - LElementType := TRttiDynamicArrayType(Self).ElementType; - Result := LElementType.IsRecord; - end; -end; - -function TRttiTypeHelper.IsObjectOfType(const AClass: TClass; - const AAllowInherithance: Boolean): Boolean; -begin - Result := False; - if IsInstance then - begin - if AAllowInherithance then - Result := TRttiInstanceType(Self).MetaclassType.InheritsFrom(AClass) - else - Result := TRttiInstanceType(Self).MetaclassType = AClass; - end; -end; - -function TRttiTypeHelper.IsObjectOfType( - const AAllowInherithance: Boolean): Boolean; -var - LType: TRttiType; -begin - Result := False; - LType := TRttiContext.Create.GetType(TypeInfo(T)); - if LType.IsInstance then - Result := IsObjectOfType((LType as TRttiInstanceType).MetaclassType, AAllowInherithance); -end; - -{ TRttiHelper } - -class function TRttiHelper.MethodParametersMatch( - const AMethod: TRttiMethod): Boolean; -begin - Result := Length(AMethod.GetParameters) = 0; -end; - -class function TRttiHelper.MethodParametersMatch( - const AMethod: TRttiMethod): Boolean; -var - LParameters: TArray; -begin - LParameters := AMethod.GetParameters; - Result := (Length(LParameters) = 4) - and (LParameters[0].ParamType.Handle = TypeInfo(A1)) - and (LParameters[1].ParamType.Handle = TypeInfo(A2)) - and (LParameters[2].ParamType.Handle = TypeInfo(A3)) - and (LParameters[3].ParamType.Handle = TypeInfo(A4)); -end; - -class function TRttiHelper.MethodParametersMatch( - const AMethod: TRttiMethod): Boolean; -var - LParameters: TArray; -begin - LParameters := AMethod.GetParameters; - Result := (Length(LParameters) = 3) - and (LParameters[0].ParamType.Handle = TypeInfo(A1)) - and (LParameters[1].ParamType.Handle = TypeInfo(A2)) - and (LParameters[2].ParamType.Handle = TypeInfo(A3)); -end; - -class function TRttiHelper.MethodParametersMatch( - const AMethod: TRttiMethod): Boolean; -var - LParameters: TArray; -begin - LParameters := AMethod.GetParameters; - Result := (Length(LParameters) = 2) - and (LParameters[0].ParamType.Handle = TypeInfo(A1)) - and (LParameters[1].ParamType.Handle = TypeInfo(A2)); -end; - -class function TRttiHelper.MethodParametersMatch( - const AMethod: TRttiMethod): Boolean; -var - LParameters: TArray; -begin - LParameters := AMethod.GetParameters; - Result := (Length(LParameters) = 1) - and (LParameters[0].ParamType.Handle = TypeInfo(A1)); -end; - -class function TRttiHelper.FindParameterLessConstructor( - const AClass: TClass): TRttiMethod; -var - LContext: TRttiContext; - LType: TRttiType; - LMethod: TRttiMethod; -begin - Result := nil; - LType := LContext.GetType(AClass); - - for LMethod in LType.GetMethods do - begin - if LMethod.IsConstructor and (Length(LMethod.GetParameters) = 0) then - begin - Result := LMethod; - Break; - end; - end; -end; - -class function TRttiHelper.ForEachAttribute(const AInstance: TObject; - const ADoSomething: TProc): Integer; -var - LContext: TRttiContext; - LType: TRttiType; -begin - Result := 0; - LType := LContext.GetType(AInstance.ClassType); - if Assigned(LType) then - Result := LType.ForEachAttribute(ADoSomething); -end; - -class function TRttiHelper.ForEachAttribute(const AAttributes: TArray; - const ADoSomething: TFunc): Integer; -var - LAttribute: TCustomAttribute; - LContinue: Boolean; -begin - Result := 0; - if not Assigned(ADoSomething) then - Exit; - - for LAttribute in AAttributes do - begin - if LAttribute.InheritsFrom(TClass(T)) then - begin - LContinue := ADoSomething(T(LAttribute)); - Inc(Result); - - if not LContinue then - Break; - end; - end; -end; - -class function TRttiHelper.ForEachAttribute(const AAttributes: TArray; - const ADoSomething: TProc): Integer; -var - LAttribute: TCustomAttribute; -begin - if Assigned(ADoSomething) then - for LAttribute in AAttributes do - begin - if LAttribute.InheritsFrom(TClass(T)) then - begin - ADoSomething(T(LAttribute)); - Inc(Result); - end; - end; -end; - -class function TRttiHelper.ForEachField(AInstance: TObject; - const ADoSomething: TFunc): Integer; -var - LContext: TRttiContext; - LField: TRttiField; - LType: TRttiType; - LBreak: Boolean; -begin - Result := 0; - LType := LContext.GetType(AInstance.ClassType); - for LField in LType.GetFields do - begin - LBreak := False; - - if Assigned(ADoSomething) then - begin - if not ADoSomething(LField) then - LBreak := True - else - Inc(Result); - end; - - if LBreak then - Break; - end; -end; - -class function TRttiHelper.ForEachFieldWithAttribute(AInstance: TObject; - const ADoSomething: TFunc): Integer; -var - LContext: TRttiContext; - LType: TRttiType; -begin - Result := 0; - LType := LContext.GetType(AInstance.ClassType); - if Assigned(LType) then - Result := LType.ForEachFieldWithAttribute(ADoSomething); -end; - -class function TRttiHelper.ForEachMethodWithAttribute( - const AMethods: TArray; - const ADoSomething: TFunc): Integer; -var - LMethod: TRttiMethod; - LBreak: Boolean; -begin - Result := 0; - if not Assigned(ADoSomething) then - Exit; - - for LMethod in AMethods do - begin - LBreak := False; - if LMethod.HasAttribute( - procedure (AAttrib: T) - begin - if not ADoSomething(LMethod, AAttrib) then - LBreak := True; - end - ) - then - Inc(Result); - - if LBreak then - Break; - end; -end; - -class function TRttiHelper.IfHasAttribute(AInstance: TObject): Boolean; -begin - Result := IfHasAttribute(AInstance, nil); -end; - -class function TRttiHelper.IfHasAttribute(AInstance: TObject; - const ADoSomething: TProc): Boolean; -var - LContext: TRttiContext; - LType: TRttiType; -begin - Result := False; - LType := LContext.GetType(AInstance.ClassType); - if Assigned(LType) then - Result := LType.HasAttribute(ADoSomething); -end; - - -{ TRecord } - -class function TRecord.DataSetToArray(const ADataSet: TDataSet): TArray; -var - LItem: R; -begin - if not ADataSet.Active then - ADataSet.Active := True - else - ADataSet.First; - -{$ifdef DelphiXE7_UP} - Result := []; -{$else} - SetLength(Result, 0); -{$endif} - - while not ADataSet.Eof do - begin - TRecord.FromDataSet(LItem, ADataSet); - -{$ifdef DelphiXE7_UP} - Result := Result + [LItem]; -{$else} - SetLength(Result, Length(Result) + 1); - Result[Length(Result)-1] := LItem; -{$endif} - - ADataSet.Next; - end; -end; - -class procedure TRecord.FromDataSet(var ARecord: R; - const ADataSet: TDataSet); -var - LRecordType: TRttiType; - LRecordField: TRttiField; - LDataSetField: TField; - LValue: TValue; -begin - if not ADataSet.Active then - ADataSet.Active := True; - - LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); - for LRecordField in LRecordType.GetFields do - begin - LDataSetField := ADataSet.FindField(LRecordField.Name); - if Assigned(LDataSetField) then - begin - if LDataSetField.IsNull then - LRecordField.SetValue(@ARecord, TValue.Empty) - - else if (LDataSetField.DataType = ftBCD) then // MF 20170726 - LRecordField.SetValue(@ARecord, LDataSetField.AsFloat) - else if (LDataSetField.DataType = ftFMTBcd) then // MF 20170726 - LRecordField.SetValue(@ARecord, LDataSetField.AsFloat) - - else if LRecordField.FieldType.Handle = TypeInfo(Boolean) then - begin - if LDataSetField.DataType = ftBoolean then - LRecordField.SetValue(@ARecord, LDataSetField.AsBoolean) - else - LRecordField.SetValue(@ARecord, LDataSetField.AsInteger <> 0); - end - else if (LRecordField.FieldType.Handle = TypeInfo(TDateTime)) - or (LRecordField.FieldType.Handle = TypeInfo(TDate)) - or (LRecordField.FieldType.Handle = TypeInfo(TTime)) - then - begin - if (LDataSetField.DataType in [ftDate, ftDateTime, ftTime, ftTimeStamp]) then - LRecordField.SetValue(@ARecord, LDataSetField.AsDateTime) - else - LRecordField.SetValue(@ARecord, StrToDateTimeDef(LDataSetField.AsString, 0)); - end - else if LRecordField.FieldType is TRttiEnumerationType then - begin - if LDataSetField is TNumericField then - LRecordField.SetValue(@ARecord, TValue.FromOrdinal(LRecordField.FieldType.Handle, LDataSetField.AsInteger)) - else if LDataSetField is TStringField then - LRecordField.SetValue(@ARecord - , TValue.FromOrdinal( - LRecordField.FieldType.Handle - , GetEnumValue(LRecordField.FieldType.Handle, LDataSetField.AsString) - ) - ); - end - else - LRecordField.SetValue(@ARecord, TValue.FromVariant(LDataSetField.Value)); - end; - end; -end; - -class function TRecord.FromStrings(const AStrings: TStrings): R; -var - LRecordType: TRttiType; -begin - LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); - Result := StringsToRecord(AStrings, LRecordType).AsType; -end; - -class function TRecord.GetFieldByName(var ARecord: R; - const AFieldName: string): TValue; -begin - Result := GetFieldByName(ARecord, AFieldName, TValue.Empty); -end; - -class function TRecord.GetFieldByName(var ARecord: R; - const AFieldName: string; const ADefault: TValue): TValue; -var - LRecordType: TRttiType; - LField: TRttiField; -begin - Result := ADefault; - LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); - LField := LRecordType.GetField(AFieldName); - if Assigned(LField) then - Result := LField.GetValue(@ARecord); -end; - -class procedure TRecord.SetFieldByName(var ARecord: R; - const AFieldName: string; const AValue: TValue); -var - LRecordType: TRttiType; - LField: TRttiField; -begin - LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); - - LField := LRecordType.GetField(AFieldName); - LField.SetValue(@ARecord, AValue); -end; - - -class function TRecord.ToArrayOfString(const ARecord: R): TArray; -var - LDummy: TStrings; -begin - LDummy := TStringList.Create; - try - ToStrings(ARecord, LDummy); - Result := LDummy.ToStringArray; - finally - LDummy.Free; - end; -end; - -class procedure TRecord.ToDataSet(const ARecord: R; const ADataSet: TDataSet; - const AAppend: Boolean); -var - LRecordType: TRttiType; - LRecordField: TRttiField; - LDataSetField: TField; - LValue: TValue; -begin - LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); - - if AAppend then - ADataSet.Append - else - ADataSet.Edit; - try - for LRecordField in LRecordType.GetFields do - begin - LDataSetField := ADataSet.FindField(LRecordField.Name); - if Assigned(LDataSetField) and not (AAppend and (LDataSetField.DataType = ftAutoInc)) then - begin - LValue := LRecordField.GetValue(@ARecord); - if LValue.IsEmpty then - LDataSetField.Clear - else - LDataSetField.Value := LValue.AsVariant; - - // set NULL for 0.0 DateTime values - if (LDataSetField.DataType in [ftDate, ftDateTime, ftTime, ftTimeStamp]) and (LDataSetField.Value = 0.0) then - LDataSetField.Clear; - end; - end; - ADataSet.Post; - except - ADataSet.Cancel; - raise; - end; -end; - -class procedure TRecord.ToStrings(const ARecord: R; var AStrings: TStrings; - const AClear: Boolean); -var - LRecordType: TRttiType; - LField: TRttiField; - LFieldType: TRttiType; - LFieldValue: TValue; - LToStringMethod: TRttiMethod; -begin - Assert(Assigned(AStrings)); - - if AClear then - AStrings.Clear; - - LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); - - for LField in LRecordType.GetFields do - begin - LFieldType := LField.FieldType; - LFieldValue := LField.GetValue(@ARecord); - - if LFieldType.IsRecord then - begin - LToStringMethod := LFieldType.GetMethod('ToString'); - if Assigned(LToStringMethod) then - AStrings.Values[LField.Name] := LToStringMethod.Invoke(LFieldValue, []).ToString - else - begin - //AM TODO recursion using ToStrings here - AStrings.Values[LField.Name] := LField.GetValue(@ARecord).ToString; - end; - end - else - AStrings.Values[LField.Name] := LField.GetValue(@ARecord).ToString; - end; -end; - -end. +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) +unit MARS.Rtti.Utils; + +{$I MARS.inc} + +interface + +uses + SysUtils, Classes, RTTI, TypInfo + , MARS.Core.JSON + , DB; + +type + TRttiObjectHelper = class helper for TRttiObject + public + function GetRttiType: TRttiType; + procedure SetValue(AInstance: Pointer; const AValue: TValue); + + function HasAttribute(const AInherited: Boolean = False): Boolean; overload; inline; + function HasAttribute( + const ADoSomething: TProc; const AInherited: Boolean = False): Boolean; overload; inline; + + function GetAllAttributes(const AInherited: Boolean = False): TArray; inline; + + function ForEachAttribute( + const ADoSomething: TProc; const AInherited: Boolean = False): Integer; overload; inline; + function ForEachAttribute( + const ADoSomething: TFunc; const AInherited: Boolean = False): Integer; overload; inline; + end; + + TRttiTypeHelper = class helper(TRttiObjectHelper) for TRttiType + protected + public + function ForEachMethodWithAttribute( + const ADoSomething: TFunc): Integer; inline; + + function ForEachFieldWithAttribute( + const ADoSomething: TFunc): Integer; + + function ForEachPropertyWithAttribute( + const ADoSomething: TFunc): Integer; + + function IsDynamicArrayOf(const AAllowInherithance: Boolean = True): Boolean; + function IsDynamicArrayOfRecord: Boolean; + function IsArray: Boolean; overload; + function IsArray(out AElementType: TRttiType): Boolean; overload; + function GetArrayElementType: TRttiType; + + function IsObjectOfType(const AAllowInherithance: Boolean = True): Boolean; overload; + function IsObjectOfType(const AClass: TClass; const AAllowInherithance: Boolean = True): Boolean; overload; + + function FindMethodFunc(const AName: string): TRttiMethod; overload; + function FindMethodFunc(const AName: string): TRttiMethod; overload; + function FindMethodFunc(const AName: string): TRttiMethod; overload; + function FindMethodFunc(const AName: string): TRttiMethod; overload; + + function FindMethodFunc(const AName: string): TRttiMethod; overload; + end; + + TRttiHelper = class + public + class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; + class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; + class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; + class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; + class function MethodParametersMatch(const AMethod: TRttiMethod): Boolean; overload; + + class function IfHasAttribute(AInstance: TObject): Boolean; overload; + class function IfHasAttribute(AInstance: TObject; const ADoSomething: TProc): Boolean; overload; + + class function ForEachAttribute(const AInstance: TObject; + const ADoSomething: TProc): Integer; overload; + + class function ForEachAttribute(const AAttributes: TArray; + const ADoSomething: TProc): Integer; overload; + class function ForEachAttribute(const AAttributes: TArray; + const ADoSomething: TFunc): Integer; overload; + + class function ForEachMethodWithAttribute(const AMethods: TArray; + const ADoSomething: TFunc): Integer; + + class function ForEachFieldWithAttribute(AInstance: TObject; const ADoSomething: TFunc): Integer; overload; + class function ForEachField(AInstance: TObject; const ADoSomething: TFunc): Integer; + + class function FindParameterLessConstructor(const AClass: TClass): TRttiMethod; + end; + + TRecord = class + public + class procedure ToDataSet(const ARecord: R; const ADataSet: TDataSet; const AAppend: Boolean = False); + class procedure FromDataSet(var ARecord: R; const ADataSet: TDataSet); + class function DataSetToArray(const ADataSet: TDataSet): TArray; + class procedure SetFieldByName(var ARecord: R; const AFieldName: string; const AValue: TValue); + class function GetFieldByName(var ARecord: R; const AFieldName: string): TValue; overload; + class function GetFieldByName(var ARecord: R; const AFieldName: string; const ADefault: TValue): TValue; overload; + class function FromStrings(const AStrings: TStrings): R; + class procedure ToStrings(const ARecord: R; var AStrings: TStrings; const AClear: Boolean = True); + class function ToArrayOfString(const ARecord: R): TArray; + end; + TOnGetRecordFieldValueProc = reference to procedure (const AName: string; const AField: TRttiField; var AValue: TValue); + + + function StringsToRecord(const AStrings: string; const ARecordType: TRttiType; + const AOnGetFieldValue: TOnGetRecordFieldValueProc = nil): TValue; overload; + + function StringsToRecord(const AStrings: TStrings; const ARecordType: TRttiType; + const AOnGetFieldValue: TOnGetRecordFieldValueProc = nil): TValue; overload; + +function ExecuteMethod(const AInstance: TValue; const AMethodName: string; const AArguments: array of TValue; + const ABeforeExecuteProc: TProc{ = nil}; const AAfterExecuteProc: TProc{ = nil}): Boolean; overload; + +function ExecuteMethod(const AInstance: TValue; AMethod: TRttiMethod; const AArguments: array of TValue; + const ABeforeExecuteProc: TProc{ = nil}; const AAfterExecuteProc: TProc{ = nil}): Boolean; overload; + +function ReadPropertyValue(AInstance: TObject; const APropertyName: string): TValue; + +procedure SetArrayLength(var AArray: TValue; const AArrayType: TRttiType; const ANewSize: PNativeInt); + +function StringToTValue(const AString: string; const ADesiredType: TRttiType): TValue; + +implementation + +uses + StrUtils, DateUtils, Generics.Collections + , MARS.Core.Utils +; + +function StringsToRecord(const AStrings: string; const ARecordType: TRttiType; + const AOnGetFieldValue: TOnGetRecordFieldValueProc = nil): TValue; +var + LSL: TStringList; +begin + LSL := TStringList.Create; + try + LSL.Text := AStrings; + Result := StringsToRecord(LSL, ARecordType, AOnGetFieldValue); + finally + LSL.Free; + end; +end; + + +function StringsToRecord(const AStrings: TStrings; const ARecordType: TRttiType; + const AOnGetFieldValue: TOnGetRecordFieldValueProc = nil): TValue; +var + LField: TRttiField; +// LValue: TValue; + LRecordInstance: Pointer; +// LFilterProc: TToRecordFilterProc; + LAccept: Boolean; + LJSONName: string; + LAssignedValuesField: TRttiField; + LAssignedValues: TArray; + LValue: TValue; + +// function GetRecordFilterProc: TToRecordFilterProc; +// var +// LMethod: TRttiMethod; +// begin +// Result := nil; +// // looking for TMyRecord.ToRecordFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; +// +// LMethod := ARecordType.FindMethodFunc('ToRecordFilter'); +// if Assigned(LMethod) then +// Result := +// procedure (const AField: TRttiField; const ARecord: TValue; const AJSONObject: TJSONObject; var AAccept: Boolean) +// begin +// AAccept := LMethod.Invoke(ARecord, [AField, AJSONObject]).AsBoolean; +// end; +// end; + +begin + TValue.Make(nil, ARecordType.Handle, Result); + LRecordInstance := Result.GetReferenceToRawData; + +// LFilterProc := AFilterProc; +// if not Assigned(LFilterProc) then +// LFilterProc := GetRecordFilterProc(); + + LAssignedValuesField := ARecordType.GetField('_AssignedValues'); + if Assigned(LAssignedValuesField) + and not LAssignedValuesField.FieldType.IsDynamicArrayOf + then + LAssignedValuesField := nil; + LAssignedValues := []; + + for LField in ARecordType.GetFields do + begin + LAccept := True; +// if Assigned(LFilterProc) then +// LFilterProc(LField, Result, Self, LAccept); + + if LAccept then + begin + LJSONName := LField.Name; + LField.HasAttribute( + procedure (AAttr: JSONNameAttribute) + begin + LJSONName := AAttr.Name; + end + ); + if LJSONName <> '' then + begin + LValue := StringToTValue(AStrings.Values[LJSONName], LField.FieldType); + if Assigned(AOnGetFieldValue) then + AOnGetFieldValue(LJSONName, LField, LValue); + LField.SetValue(LRecordInstance, LValue); + LAssignedValues := LAssignedValues + [LField.Name]; + end; + end; + end; + if Assigned(LAssignedValuesField) then + LAssignedValuesField.SetValue(LRecordInstance, TValue.From>(LAssignedValues)); +end; + +function StringToTValue(const AString: string; const ADesiredType: TRttiType): TValue; +begin + if ADesiredType.IsObjectOfType then + Result := TJSONObject.ParseJSONValue(AString) + else + begin + case ADesiredType.TypeKind of + tkInt64: Result := StrToInt64Def(AString, 0); + tkInteger: Result := StrToIntDef(AString, 0); + tkEnumeration: begin + if SameText(ADesiredType.Name, 'Boolean') then + Result := StrToBoolDef(AString, False); + end; + tkFloat: begin + if IndexStr(ADesiredType.Name, ['TDate', 'TDateTime', 'TTime']) <> -1 then + begin + try + Result := ISO8601ToDate(AString); + except + Result := StrToDateTime(AString) + end; + end + else + begin + Result := StrToFloatDef(AString + , StrToFloatDef(AString, 0.0, TFormatSettings.Create('en')) // second chance + ); + end; + end; + + {$ifdef DelphiXE7_UP} + tkChar: begin + if AString.IsEmpty then + Result := '' + else + Result := TValue.From(AString.Chars[0]); + end; + {$else} + tkChar: Result := TValue.From(Copy(AString, 1, 1)); + {$endif} + else + Result := AString; + end; + end; +end; + +procedure SetArrayLength(var AArray: TValue; const AArrayType: TRttiType; const ANewSize: PNativeInt); +begin + if AArrayType is TRttiArrayType then + begin + raise Exception.Create('Not yet implemented: SetArrayLength TRttiArrayType'); + { TODO -oAndrea : probably not needed } + end + else if AArrayType is TRttiDynamicArrayType then + DynArraySetLength(PPointer(AArray.GetReferenceToRawData)^, AArrayType.Handle, 1, ANewSize); +end; + +function ReadPropertyValue(AInstance: TObject; const APropertyName: string): TValue; +var + LContext: TRttiContext; + LType: TRttiType; + LProperty: TRttiProperty; +begin + Result := TValue.Empty; + LType := LContext.GetType(AInstance.ClassType); + if Assigned(LType) then + begin + LProperty := LType.GetProperty(APropertyName); + if Assigned(LProperty) then + Result := LProperty.GetValue(AInstance); + end; +end; + +function ExecuteMethod(const AInstance: TValue; AMethod: TRttiMethod; + const AArguments: array of TValue; const ABeforeExecuteProc: TProc{ = nil}; + const AAfterExecuteProc: TProc{ = nil}): Boolean; +var + LResult: TValue; +begin + if Assigned(ABeforeExecuteProc) then + ABeforeExecuteProc(); + LResult := AMethod.Invoke(AInstance, AArguments); + Result := True; + if Assigned(AAfterExecuteProc) then + AAfterExecuteProc(LResult); +end; + +function ExecuteMethod(const AInstance: TValue; const AMethodName: string; + const AArguments: array of TValue; const ABeforeExecuteProc: TProc{ = nil}; + const AAfterExecuteProc: TProc{ = nil}): Boolean; +var + LContext: TRttiContext; + LType: TRttiType; + LMethod: TRttiMethod; +begin + Result := False; + LType := LContext.GetType(AInstance.TypeInfo); + if Assigned(LType) then + begin + LMethod := LType.GetMethod(AMethodName); + if Assigned(LMethod) then + Result := ExecuteMethod(AInstance, LMethod, AArguments, ABeforeExecuteProc, AAfterExecuteProc); + end; +end; + +{ TRttiObjectHelper } + +function TRttiObjectHelper.ForEachAttribute( + const ADoSomething: TProc; const AInherited: Boolean): Integer; +begin + Result := TRttiHelper.ForEachAttribute(GetAllAttributes(AInherited), ADoSomething); +end; + +function TRttiObjectHelper.HasAttribute(const AInherited: Boolean): Boolean; +begin + Result := HasAttribute(nil, AInherited); +end; + +function TRttiObjectHelper.ForEachAttribute( + const ADoSomething: TFunc; const AInherited: Boolean): Integer; +begin + Result := TRttiHelper.ForEachAttribute(GetAllAttributes(AInherited), ADoSomething); +end; + +function TRttiObjectHelper.GetAllAttributes( + const AInherited: Boolean): TArray; +var + LBaseType: TRttiType; + LType: TRttiType; +begin + Result := Self.GetAttributes; + + { TODO -oAndrea : Implement AInherited = True behavior when Self is a TRttiMethod instance } + LType := Self.GetRttiType; + if AInherited and Assigned(LType) then + begin + LBaseType := LType.BaseType; + while Assigned(LBaseType) do + begin + Result := Result + LBaseType.GetAttributes; + LBaseType := LBaseType.BaseType; + end; + end; +end; + +function TRttiObjectHelper.GetRttiType: TRttiType; +begin + Result := nil; + if Self is TRttiField then + Result := TRttiField(Self).FieldType + else if Self is TRttiProperty then + Result := TRttiProperty(Self).PropertyType + else if Self is TRttiParameter then + Result := TRttiParameter(Self).ParamType + else if Self is TRttiType then + Result := TRttiType(Self); +end; + +function TRttiObjectHelper.HasAttribute( + const ADoSomething: TProc; const AInherited: Boolean): Boolean; +var + LAttribute: TCustomAttribute; + LResult: Boolean; +begin + LResult := False; + + ForEachAttribute( + function (AAttr: T): Boolean + begin + Result := False; + LResult := True; + if Assigned(ADoSomething) then + ADoSomething(AAttr); + end + , AInherited + ); + + Result := LResult; +end; + +procedure TRttiObjectHelper.SetValue(AInstance: Pointer; const AValue: TValue); +begin + if Self is TRttiField then + TRttiField(Self).SetValue(AInstance, AValue) + else if Self is TRttiProperty then + TRttiProperty(Self).SetValue(AInstance, AValue) + else if Self is TRttiParameter then + raise ENotSupportedException.Create('Setting value of TRttiParameter not supported'); +end; + +{ TRttiTypeHelper } + +function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; +var + LMethod: TRttiMethod; + LParameters: TArray; +begin + LMethod := GetMethod(AName); + if Assigned(LMethod) then + begin + if not ( + (LMethod.ReturnType.Handle = TypeInfo(R)) + and TRttiHelper.MethodParametersMatch(LMethod) + ) then + LMethod := nil; + end; + + Result := LMethod; +end; + +function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; +var + LMethod: TRttiMethod; + LParameters: TArray; +begin + LMethod := GetMethod(AName); + if Assigned(LMethod) then + begin + if not ( + (LMethod.ReturnType.Handle = TypeInfo(R)) + and TRttiHelper.MethodParametersMatch(LMethod) + ) then + LMethod := nil; + end; + + Result := LMethod; +end; + +function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; +var + LMethod: TRttiMethod; + LParameters: TArray; +begin + LMethod := GetMethod(AName); + if Assigned(LMethod) then + begin + if not ( + (LMethod.ReturnType.Handle = TypeInfo(R)) + and TRttiHelper.MethodParametersMatch(LMethod) + ) then + LMethod := nil; + end; + + Result := LMethod; +end; + +function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; +var + LMethod: TRttiMethod; + LParameters: TArray; +begin + LMethod := GetMethod(AName); + if Assigned(LMethod) then + begin + if not ( + (LMethod.ReturnType.Handle = TypeInfo(R)) + and TRttiHelper.MethodParametersMatch(LMethod) + ) then + LMethod := nil; + end; + + Result := LMethod; +end; + +function TRttiTypeHelper.FindMethodFunc(const AName: string): TRttiMethod; +var + LMethod: TRttiMethod; + LParameters: TArray; +begin + LMethod := GetMethod(AName); + if Assigned(LMethod) then + begin + if not ( + (LMethod.ReturnType.Handle = TypeInfo(R)) + and TRttiHelper.MethodParametersMatch(LMethod) + ) then + LMethod := nil; + end; + + Result := LMethod; +end; + +function TRttiTypeHelper.ForEachFieldWithAttribute( + const ADoSomething: TFunc): Integer; +var + LField: TRttiField; + LBreak: Boolean; +begin + for LField in Self.GetFields do + begin + LBreak := False; + if LField.HasAttribute( + procedure (AAttrib: T) + begin + if Assigned(ADoSomething) then + begin + if not ADoSomething(LField, AAttrib) then + LBreak := True; + end; + end + ) + then + Inc(Result); + + if LBreak then + Break; + end; +end; + +function TRttiTypeHelper.ForEachMethodWithAttribute( + const ADoSomething: TFunc): Integer; +begin + Result := TRttiHelper.ForEachMethodWithAttribute(Self.GetMethods, ADoSomething); +end; + +function TRttiTypeHelper.ForEachPropertyWithAttribute( + const ADoSomething: TFunc): Integer; +var + LProperty: TRttiProperty; + LBreak: Boolean; +begin + Result := 0; + for LProperty in Self.GetProperties do + begin + LBreak := False; + if LProperty.HasAttribute( + procedure (AAttrib: T) + begin + if Assigned(ADoSomething) then + begin + if not ADoSomething(LProperty, AAttrib) then + LBreak := True; + end; + end + ) + then + Inc(Result); + + if LBreak then + Break; + end; +end; + +function TRttiTypeHelper.GetArrayElementType: TRttiType; +begin + Result := nil; + if Self is TRttiDynamicArrayType then + Result := TRttiDynamicArrayType(Self).ElementType; +end; + +function TRttiTypeHelper.IsArray: Boolean; +begin + Result := (Self is TRttiArrayType) or (Self is TRttiDynamicArrayType); +end; + +function TRttiTypeHelper.IsArray(out AElementType: TRttiType): Boolean; +begin + Result := False; + if (Self is TRttiDynamicArrayType) then + begin + AElementType := TRttiDynamicArrayType(Self).ElementType; + Result := True; + end + else if (Self is TRttiArrayType) then + begin + AElementType := TRttiArrayType(Self).ElementType; + Result := True; + end; +end; + +function TRttiTypeHelper.IsDynamicArrayOf( + const AAllowInherithance: Boolean): Boolean; +var + LElementType: TRttiType; + LType: TRttiType; +begin + Result := False; + if Self is TRttiDynamicArrayType then + begin + LType := TRttiContext.Create.GetType(TypeInfo(T)); + LElementType := TRttiDynamicArrayType(Self).ElementType; + + Result := (LElementType = LType) // exact match + or ( // classes with inheritance check (wrt AAllowInheritance argument) + (LElementType.IsInstance and LType.IsInstance) + and LElementType.IsObjectOfType(TRttiInstanceType(LType).MetaclassType, AAllowInherithance) + ); + end; +end; + +function TRttiTypeHelper.IsDynamicArrayOfRecord: Boolean; +var + LElementType: TRttiType; +begin + Result := False; + if Self is TRttiDynamicArrayType then + begin + LElementType := TRttiDynamicArrayType(Self).ElementType; + Result := LElementType.IsRecord; + end; +end; + +function TRttiTypeHelper.IsObjectOfType(const AClass: TClass; + const AAllowInherithance: Boolean): Boolean; +begin + Result := False; + if IsInstance then + begin + if AAllowInherithance then + Result := TRttiInstanceType(Self).MetaclassType.InheritsFrom(AClass) + else + Result := TRttiInstanceType(Self).MetaclassType = AClass; + end; +end; + +function TRttiTypeHelper.IsObjectOfType( + const AAllowInherithance: Boolean): Boolean; +var + LType: TRttiType; +begin + Result := False; + LType := TRttiContext.Create.GetType(TypeInfo(T)); + if LType.IsInstance then + Result := IsObjectOfType((LType as TRttiInstanceType).MetaclassType, AAllowInherithance); +end; + +{ TRttiHelper } + +class function TRttiHelper.MethodParametersMatch( + const AMethod: TRttiMethod): Boolean; +begin + Result := Length(AMethod.GetParameters) = 0; +end; + +class function TRttiHelper.MethodParametersMatch( + const AMethod: TRttiMethod): Boolean; +var + LParameters: TArray; +begin + LParameters := AMethod.GetParameters; + Result := (Length(LParameters) = 4) + and (LParameters[0].ParamType.Handle = TypeInfo(A1)) + and (LParameters[1].ParamType.Handle = TypeInfo(A2)) + and (LParameters[2].ParamType.Handle = TypeInfo(A3)) + and (LParameters[3].ParamType.Handle = TypeInfo(A4)); +end; + +class function TRttiHelper.MethodParametersMatch( + const AMethod: TRttiMethod): Boolean; +var + LParameters: TArray; +begin + LParameters := AMethod.GetParameters; + Result := (Length(LParameters) = 3) + and (LParameters[0].ParamType.Handle = TypeInfo(A1)) + and (LParameters[1].ParamType.Handle = TypeInfo(A2)) + and (LParameters[2].ParamType.Handle = TypeInfo(A3)); +end; + +class function TRttiHelper.MethodParametersMatch( + const AMethod: TRttiMethod): Boolean; +var + LParameters: TArray; +begin + LParameters := AMethod.GetParameters; + Result := (Length(LParameters) = 2) + and (LParameters[0].ParamType.Handle = TypeInfo(A1)) + and (LParameters[1].ParamType.Handle = TypeInfo(A2)); +end; + +class function TRttiHelper.MethodParametersMatch( + const AMethod: TRttiMethod): Boolean; +var + LParameters: TArray; +begin + LParameters := AMethod.GetParameters; + Result := (Length(LParameters) = 1) + and (LParameters[0].ParamType.Handle = TypeInfo(A1)); +end; + +class function TRttiHelper.FindParameterLessConstructor( + const AClass: TClass): TRttiMethod; +var + LContext: TRttiContext; + LType: TRttiType; + LMethod: TRttiMethod; +begin + Result := nil; + LType := LContext.GetType(AClass); + + for LMethod in LType.GetMethods do + begin + if LMethod.IsConstructor and (Length(LMethod.GetParameters) = 0) then + begin + Result := LMethod; + Break; + end; + end; +end; + +class function TRttiHelper.ForEachAttribute(const AInstance: TObject; + const ADoSomething: TProc): Integer; +var + LContext: TRttiContext; + LType: TRttiType; +begin + Result := 0; + LType := LContext.GetType(AInstance.ClassType); + if Assigned(LType) then + Result := LType.ForEachAttribute(ADoSomething); +end; + +class function TRttiHelper.ForEachAttribute(const AAttributes: TArray; + const ADoSomething: TFunc): Integer; +var + LAttribute: TCustomAttribute; + LContinue: Boolean; +begin + Result := 0; + if not Assigned(ADoSomething) then + Exit; + + for LAttribute in AAttributes do + begin + if LAttribute.InheritsFrom(TClass(T)) then + begin + LContinue := ADoSomething(T(LAttribute)); + Inc(Result); + + if not LContinue then + Break; + end; + end; +end; + +class function TRttiHelper.ForEachAttribute(const AAttributes: TArray; + const ADoSomething: TProc): Integer; +var + LAttribute: TCustomAttribute; +begin + if Assigned(ADoSomething) then + for LAttribute in AAttributes do + begin + if LAttribute.InheritsFrom(TClass(T)) then + begin + ADoSomething(T(LAttribute)); + Inc(Result); + end; + end; +end; + +class function TRttiHelper.ForEachField(AInstance: TObject; + const ADoSomething: TFunc): Integer; +var + LContext: TRttiContext; + LField: TRttiField; + LType: TRttiType; + LBreak: Boolean; +begin + Result := 0; + LType := LContext.GetType(AInstance.ClassType); + for LField in LType.GetFields do + begin + LBreak := False; + + if Assigned(ADoSomething) then + begin + if not ADoSomething(LField) then + LBreak := True + else + Inc(Result); + end; + + if LBreak then + Break; + end; +end; + +class function TRttiHelper.ForEachFieldWithAttribute(AInstance: TObject; + const ADoSomething: TFunc): Integer; +var + LContext: TRttiContext; + LType: TRttiType; +begin + Result := 0; + LType := LContext.GetType(AInstance.ClassType); + if Assigned(LType) then + Result := LType.ForEachFieldWithAttribute(ADoSomething); +end; + +class function TRttiHelper.ForEachMethodWithAttribute( + const AMethods: TArray; + const ADoSomething: TFunc): Integer; +var + LMethod: TRttiMethod; + LBreak: Boolean; +begin + Result := 0; + if not Assigned(ADoSomething) then + Exit; + + for LMethod in AMethods do + begin + LBreak := False; + if LMethod.HasAttribute( + procedure (AAttrib: T) + begin + if not ADoSomething(LMethod, AAttrib) then + LBreak := True; + end + ) + then + Inc(Result); + + if LBreak then + Break; + end; +end; + +class function TRttiHelper.IfHasAttribute(AInstance: TObject): Boolean; +begin + Result := IfHasAttribute(AInstance, nil); +end; + +class function TRttiHelper.IfHasAttribute(AInstance: TObject; + const ADoSomething: TProc): Boolean; +var + LContext: TRttiContext; + LType: TRttiType; +begin + Result := False; + LType := LContext.GetType(AInstance.ClassType); + if Assigned(LType) then + Result := LType.HasAttribute(ADoSomething); +end; + + +{ TRecord } + +class function TRecord.DataSetToArray(const ADataSet: TDataSet): TArray; +var + LItem: R; +begin + if not ADataSet.Active then + ADataSet.Active := True + else + ADataSet.First; + +{$ifdef DelphiXE7_UP} + Result := []; +{$else} + SetLength(Result, 0); +{$endif} + + while not ADataSet.Eof do + begin + TRecord.FromDataSet(LItem, ADataSet); + +{$ifdef DelphiXE7_UP} + Result := Result + [LItem]; +{$else} + SetLength(Result, Length(Result) + 1); + Result[Length(Result)-1] := LItem; +{$endif} + + ADataSet.Next; + end; +end; + +class procedure TRecord.FromDataSet(var ARecord: R; + const ADataSet: TDataSet); +var + LRecordType: TRttiType; + LRecordField: TRttiField; + LDataSetField: TField; + LValue: TValue; +begin + if not ADataSet.Active then + ADataSet.Active := True; + + LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); + for LRecordField in LRecordType.GetFields do + begin + LDataSetField := ADataSet.FindField(LRecordField.Name); + if Assigned(LDataSetField) then + begin + if LDataSetField.IsNull then + LRecordField.SetValue(@ARecord, TValue.Empty) + + else if (LDataSetField.DataType = ftBCD) then // MF 20170726 + LRecordField.SetValue(@ARecord, LDataSetField.AsFloat) + else if (LDataSetField.DataType = ftFMTBcd) then // MF 20170726 + LRecordField.SetValue(@ARecord, LDataSetField.AsFloat) + + else if LRecordField.FieldType.Handle = TypeInfo(Boolean) then + begin + if LDataSetField.DataType = ftBoolean then + LRecordField.SetValue(@ARecord, LDataSetField.AsBoolean) + else + LRecordField.SetValue(@ARecord, LDataSetField.AsInteger <> 0); + end + else if (LRecordField.FieldType.Handle = TypeInfo(TDateTime)) + or (LRecordField.FieldType.Handle = TypeInfo(TDate)) + or (LRecordField.FieldType.Handle = TypeInfo(TTime)) + then + begin + if (LDataSetField.DataType in [ftDate, ftDateTime, ftTime, ftTimeStamp]) then + LRecordField.SetValue(@ARecord, LDataSetField.AsDateTime) + else + LRecordField.SetValue(@ARecord, StrToDateTimeDef(LDataSetField.AsString, 0)); + end + else if LRecordField.FieldType is TRttiEnumerationType then + begin + if LDataSetField is TNumericField then + LRecordField.SetValue(@ARecord, TValue.FromOrdinal(LRecordField.FieldType.Handle, LDataSetField.AsInteger)) + else if LDataSetField is TStringField then + LRecordField.SetValue(@ARecord + , TValue.FromOrdinal( + LRecordField.FieldType.Handle + , GetEnumValue(LRecordField.FieldType.Handle, LDataSetField.AsString) + ) + ); + end + else + LRecordField.SetValue(@ARecord, TValue.FromVariant(LDataSetField.Value)); + end; + end; +end; + +class function TRecord.FromStrings(const AStrings: TStrings): R; +var + LRecordType: TRttiType; +begin + LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); + Result := StringsToRecord(AStrings, LRecordType).AsType; +end; + +class function TRecord.GetFieldByName(var ARecord: R; + const AFieldName: string): TValue; +begin + Result := GetFieldByName(ARecord, AFieldName, TValue.Empty); +end; + +class function TRecord.GetFieldByName(var ARecord: R; + const AFieldName: string; const ADefault: TValue): TValue; +var + LRecordType: TRttiType; + LField: TRttiField; +begin + Result := ADefault; + LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); + LField := LRecordType.GetField(AFieldName); + if Assigned(LField) then + Result := LField.GetValue(@ARecord); +end; + +class procedure TRecord.SetFieldByName(var ARecord: R; + const AFieldName: string; const AValue: TValue); +var + LRecordType: TRttiType; + LField: TRttiField; +begin + LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); + + LField := LRecordType.GetField(AFieldName); + LField.SetValue(@ARecord, AValue); +end; + + +class function TRecord.ToArrayOfString(const ARecord: R): TArray; +var + LDummy: TStrings; +begin + LDummy := TStringList.Create; + try + ToStrings(ARecord, LDummy); + Result := LDummy.ToStringArray; + finally + LDummy.Free; + end; +end; + +class procedure TRecord.ToDataSet(const ARecord: R; const ADataSet: TDataSet; + const AAppend: Boolean); +var + LRecordType: TRttiType; + LRecordField: TRttiField; + LDataSetField: TField; + LValue: TValue; +begin + LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); + + if AAppend then + ADataSet.Append + else + ADataSet.Edit; + try + for LRecordField in LRecordType.GetFields do + begin + LDataSetField := ADataSet.FindField(LRecordField.Name); + if Assigned(LDataSetField) and not (AAppend and (LDataSetField.DataType = ftAutoInc)) then + begin + LValue := LRecordField.GetValue(@ARecord); + if LValue.IsEmpty then + LDataSetField.Clear + else + LDataSetField.Value := LValue.AsVariant; + + // set NULL for 0.0 DateTime values + if (LDataSetField.DataType in [ftDate, ftDateTime, ftTime, ftTimeStamp]) and (LDataSetField.Value = 0.0) then + LDataSetField.Clear; + end; + end; + ADataSet.Post; + except + ADataSet.Cancel; + raise; + end; +end; + +class procedure TRecord.ToStrings(const ARecord: R; var AStrings: TStrings; + const AClear: Boolean); +var + LRecordType: TRttiType; + LField: TRttiField; + LFieldType: TRttiType; + LFieldValue: TValue; + LToStringMethod: TRttiMethod; +begin + Assert(Assigned(AStrings)); + + if AClear then + AStrings.Clear; + + LRecordType := TRttiContext.Create.GetType(TypeInfo(R)); + + for LField in LRecordType.GetFields do + begin + LFieldType := LField.FieldType; + LFieldValue := LField.GetValue(@ARecord); + + if LFieldType.IsRecord then + begin + LToStringMethod := LFieldType.GetMethod('ToString'); + if Assigned(LToStringMethod) then + AStrings.Values[LField.Name] := LToStringMethod.Invoke(LFieldValue, []).ToString + else + begin + //AM TODO recursion using ToStrings here + AStrings.Values[LField.Name] := LField.GetValue(@ARecord).ToString; + end; + end + else + AStrings.Values[LField.Name] := LField.GetValue(@ARecord).ToString; + end; +end; + +end. diff --git a/Source/MARS.Utils.Parameters.pas b/Source/MARS.Utils.Parameters.pas index 6599587b..6168b7b0 100644 --- a/Source/MARS.Utils.Parameters.pas +++ b/Source/MARS.Utils.Parameters.pas @@ -42,6 +42,9 @@ TMARSParametersSlice = class function CopyFrom(const ASource: TMARSParametersSlice; const ASliceName: string = ''): Integer; function ToString: string; override; + procedure AsStrings(var AStrings: TStrings; const AClearBefore: Boolean = True); overload; + function AsStrings: TStrings; overload; + function AsStringArray(const ANameValueSeparator: string = '='): TArray; property Count: Integer read GetCount; property IsEmpty: Boolean read GetIsEmpty; @@ -87,6 +90,33 @@ procedure TMARSParametersSlice.Assign(const ASource: TMARSParametersSlice); Fitems.Add(LItem.Key, LItem.Value); end; +function TMARSParametersSlice.AsStringArray( + const ANameValueSeparator: string): TArray; +var + LItem: TPair; +begin + Result := []; + for LItem in FItems do + begin + Result := Result + [LItem.Key + ANameValueSeparator + LItem.Value.ToString]; + end; +end; + +procedure TMARSParametersSlice.AsStrings(var AStrings: TStrings; + const AClearBefore: Boolean); +begin + if AClearBefore then + AStrings.Clear; + + AStrings.AddStrings(AsStringArray(AStrings.NameValueSeparator)); +end; + +function TMARSParametersSlice.AsStrings: TStrings; +begin + Result := TStringList.Create; + AsStrings(Result); +end; + function TMARSParametersSlice.ByName(const AName: string; const ADefault: TValue): TValue; var @@ -289,16 +319,8 @@ procedure TMARSParametersSlice.SetValue(AName: string; const Value: TValue); end; function TMARSParametersSlice.ToString: string; -var - LItem: TPair; begin - Result := ''; - for LItem in FItems do - begin - if Result <> '' then - Result := Result + sLineBreak; - Result := Result + LItem.Key +': ' + LItem.Value.ToString; - end; + Result := string.Join(sLineBreak, AsStringArray(': ')); end; end. diff --git a/Source/MARS.http.Server.DCS.pas b/Source/MARS.http.Server.DCS.pas new file mode 100644 index 00000000..8d2f346b --- /dev/null +++ b/Source/MARS.http.Server.DCS.pas @@ -0,0 +1,568 @@ +(* + Copyright 2016, MARS-Curiosity library + + Home: https://github.com/andrea-magni/MARS +*) +unit MARS.http.Server.DCS; + +{$I MARS.inc} + +interface + +uses + System.SysUtils, System.Classes, System.Generics.Collections, System.TimeSpan, DateUtils +, Net.CrossSocket.Base, Net.CrossSocket, Net.CrossHttpServer +, Net.CrossHttpMiddleware, Net.CrossHttpUtils +, MARS.Core.Engine, MARS.Core.Token +, MARS.Core.RequestAndResponse.Interfaces +; + +type + TMARSDCSRequest = class(TInterfacedObject, IMARSRequest) + private + FDCSRequest: ICrossHttpRequest; + public + // IMARSRequest ------------------------------------------------------------ + function AsObject: TObject; + function GetAccept: string; + function GetAuthorization: string; + function GetContent: string; + function GetCookieParamIndex(const AName: string): Integer; + function GetCookieParamValue(const AIndex: Integer): string; overload; + function GetCookieParamValue(const AName: string): string; overload; + function GetCookieParamCount: Integer; + function GetFilesCount: Integer; + function GetFormParamCount: Integer; + function GetFormParamIndex(const AName: string): Integer; + function GetFormParamName(const AIndex: Integer): string; + function GetFormParamValue(const AIndex: Integer): string; overload; + function GetFormParamValue(const AName: string): string; overload; + function GetFormFileParamIndex(const AName: string): Integer; + function GetFormFileParam(const AIndex: Integer; out AFieldName: string; + out AFileName: string; out ABytes: System.TArray; + out AContentType: string): Boolean; + function GetFormParams: string; + function GetHeaderParamValue(const AHeaderName: string): string; + function GetHostName: string; + function GetMethod: string; + function GetPort: Integer; + function GetQueryParamIndex(const AName: string): Integer; + function GetQueryParamValue(const AIndex: Integer): string; + function GetQueryParamCount: Integer; + function GetQueryString: string; + function GetRawContent: TBytes; + function GetRawPath: string; + procedure CheckWorkaroundForISAPI; + // ------------------------------------------------------------------------- + constructor Create(ADCSRequest: ICrossHttpRequest); virtual; + end; + + TMARSDCSResponse = class(TInterfacedObject, IMARSResponse) + private + FDCSResponse: ICrossHttpResponse; + public + // IMARSResponse ----------------------------------------------------------- + function GetContent: string; + function GetContentEncoding: string; + function GetContentStream: TStream; + function GetContentType: string; + function GetStatusCode: Integer; + procedure SetContent(const AContent: string); + procedure SetContentEncoding(const AContentEncoding: string); + procedure SetContentStream(const AContentStream: TStream); + procedure SetContentType(const AContentType: string); + procedure SetHeader(const AName: string; const AValue: string); + procedure SetStatusCode(const AStatusCode: Integer); + procedure SetCookie(const AName, AValue, ADomain, APath: string; const AExpiration: TDateTime; const ASecure: Boolean); + // ------------------------------------------------------------------------- + constructor Create(ADCSResponse: ICrossHttpResponse); virtual; + end; + + + TMARShttpServerDCS = class + private + FStoppedAt: TDateTime; + FEngine: TMARSEngine; + FStartedAt: TDateTime; + FActive: Boolean; + FDefaultPort: Integer; + FHttpServer: ICrossHttpServer; + function GetUpTime: TTimeSpan; + procedure SetActive(const Value: Boolean); + procedure SetDefaultPort(const Value: Integer); + protected + procedure Startup; virtual; + procedure Shutdown; virtual; + public + constructor Create(AEngine: TMARSEngine); virtual; + destructor Destroy; override; + + property Active: Boolean read FActive write SetActive; + property DefaultPort: Integer read FDefaultPort write SetDefaultPort; + property Engine: TMARSEngine read FEngine; + property StartedAt: TDateTime read FStartedAt; + property StoppedAt: TDateTime read FStoppedAt; + property UpTime: TTimeSpan read GetUpTime; + end; + +implementation + +uses + Net.CrossHttpParams; + +{ TMARShttpServerDCS } + +constructor TMARShttpServerDCS.Create(AEngine: TMARSEngine); +begin + inherited Create; + FEngine := AEngine; + FHttpServer := TCrossHttpServer.Create(0); +end; + +destructor TMARShttpServerDCS.Destroy; +begin + FHttpServer := nil; + inherited; +end; + +function TMARShttpServerDCS.GetUpTime: TTimeSpan; +begin + if Active then + Result := TTimeSpan.FromSeconds(SecondsBetween(FStartedAt, Now)) + else if StoppedAt > 0 then + Result := TTimeSpan.FromSeconds(SecondsBetween(FStartedAt, FStoppedAt)) + else + Result := TTimeSpan.Zero; +end; + +procedure TMARShttpServerDCS.SetActive(const Value: Boolean); +begin + if FActive <> Value then + begin + FActive := Value; + if FActive then + Startup + else + Shutdown; + end; +end; + +procedure TMARShttpServerDCS.SetDefaultPort(const Value: Integer); +begin + FDefaultPort := Value; +end; + +procedure TMARShttpServerDCS.Shutdown; +begin + FHttpServer.Stop; + + FStoppedAt := Now; +end; + +procedure TMARShttpServerDCS.Startup; +begin + FHttpServer.Addr := IPv4v6_ALL; // IPv4v6 + FHttpServer.Port := DefaultPort; + FHttpServer.Compressible := True; + + FHttpServer +// .Get('/hello', +// procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse) +// begin +// AResponse.Send('Hello World'); +// end) + .All(FEngine.BasePath + '*', + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + begin + AHandled := FEngine.HandleRequest(TMARSDCSRequest.Create(ARequest), TMARSDCSResponse.Create(AResponse)) + end + ); + + FHttpServer.Start; + + FStartedAt := Now; + FStoppedAt := 0; +end; + +{ TMARSDCSRequest } + +function TMARSDCSRequest.AsObject: TObject; +begin + Result := Self; +end; + +procedure TMARSDCSRequest.CheckWorkaroundForISAPI; +begin + // nothing to do +end; + +constructor TMARSDCSRequest.Create(ADCSRequest: ICrossHttpRequest); +begin + inherited Create; + FDCSRequest := ADCSRequest; +end; + +function TMARSDCSRequest.GetAccept: string; +begin + Result := FDCSRequest.Accept; +end; + +function TMARSDCSRequest.GetAuthorization: string; +begin + Result := FDCSRequest.Authorization; +end; + +function TMARSDCSRequest.GetContent: string; +begin +//AM TODO + Result := ''; +end; + +function TMARSDCSRequest.GetCookieParamCount: Integer; +begin + Result := FDCSRequest.Cookies.Count; +end; + +function TMARSDCSRequest.GetCookieParamIndex(const AName: string): Integer; +var + LIndex: Integer; +begin + Result := -1; + for LIndex := 0 to FDCSRequest.Cookies.Count -1 do + begin + if SameText(FDCSRequest.Cookies.Items[LIndex].Name, AName) then + begin + Result := LIndex; + Break; + end; + end; +end; + +function TMARSDCSRequest.GetCookieParamValue(const AName: string): string; +var + LIndex: Integer; + LCookie: TNameValue; +begin + Result := ''; + for LIndex := 0 to FDCSRequest.Cookies.Count -1 do + begin + LCookie := FDCSRequest.Cookies.Items[LIndex]; + if SameText(LCookie.Name, AName) then + begin + Result := LCookie.Value; + Break; + end; + end; +end; + +function TMARSDCSRequest.GetCookieParamValue(const AIndex: Integer): string; +begin + Result := FDCSRequest.Cookies.Items[AIndex].Value; +end; + +function TMARSDCSRequest.GetFilesCount: Integer; +begin +//AM TODO + Result := 0; +end; + +function TMARSDCSRequest.GetFormFileParam(const AIndex: Integer; out AFieldName, + AFileName: string; out ABytes: System.TArray; + out AContentType: string): Boolean; +var + LMultiPartBody: THttpMultiPartFormData; + LFile: TFormField; + +begin + Result := False; + if FDCSRequest.BodyType = btMultiPart then + begin + LMultiPartBody := FDCSRequest.Body as THttpMultiPartFormData; + Result := (AIndex >= 0) and (AIndex < LMultiPartBody.Count); + if Result then + begin + LFile := LMultiPartBody.Items[AIndex]; + AFieldName := LFile.Name; + AFileName := LFile.FileName; + ABytes := LFile.AsBytes; + AContentType := LFile.ContentType; + end; + end; +end; + +function TMARSDCSRequest.GetFormFileParamIndex(const AName: string): Integer; +var + LMultiPartBody: THttpMultiPartFormData; + LIndex: Integer; + LItem: TFormField; + LURLParamsBody: THttpUrlParams; + LURLParamItem: TNameValue; +begin + Result := -1; + if FDCSRequest.BodyType = btMultiPart then + begin + LMultiPartBody := FDCSRequest.Body as THttpMultiPartFormData; + + for LIndex := 0 to LMultiPartBody.Count - 1 do + begin + LItem := LMultiPartBody.Items[LIndex]; + + if SameText(LItem.Name, AName) then + begin + Result := LIndex; + Break; + end; + end; + end + else if FDCSRequest.BodyType = btUrlEncoded then + begin + LURLParamsBody := FDCSRequest.Body as THttpUrlParams; + + for LIndex := 0 to LURLParamsBody.Count-1 do + begin + LURLParamItem := LURLParamsBody.Items[LIndex]; + if SameText(LURLParamItem.Name, AName) then + begin + Result := LIndex; + Break; + end; + end; + end; +end; + +function TMARSDCSRequest.GetFormParamCount: Integer; +begin +//AM TODO + Result := 0; +end; + +function TMARSDCSRequest.GetFormParamIndex(const AName: string): Integer; +var + LMultiPartBody: THttpMultiPartFormData; + LIndex: Integer; + LItem: TFormField; + LURLParamsBody: THttpUrlParams; + LURLParamItem: TNameValue; +begin + Result := -1; + if FDCSRequest.BodyType = btMultiPart then + begin + LMultiPartBody := FDCSRequest.Body as THttpMultiPartFormData; + + for LIndex := 0 to LMultiPartBody.Count - 1 do + begin + LItem := LMultiPartBody.Items[LIndex]; + + if SameText(LItem.Name, AName) then + begin + Result := LIndex; + Break; + end; + end; + end + else if FDCSRequest.BodyType = btUrlEncoded then + begin + LURLParamsBody := FDCSRequest.Body as THttpUrlParams; + + for LIndex := 0 to LURLParamsBody.Count-1 do + begin + LURLParamItem := LURLParamsBody.Items[LIndex]; + if SameText(LURLParamItem.Name, AName) then + begin + Result := LIndex; + Break; + end; + end; + end; +end; + +function TMARSDCSRequest.GetFormParamName(const AIndex: Integer): string; +var + LMultiPartBody: THttpMultiPartFormData; + LURLParamsBody: THttpUrlParams; +begin + Result := ''; + if FDCSRequest.BodyType = btMultiPart then + begin + LMultiPartBody := FDCSRequest.Body as THttpMultiPartFormData; + Result := LMultiPartBody.Items[AIndex].Name; + end + else if FDCSRequest.BodyType = btUrlEncoded then + begin + LURLParamsBody := FDCSRequest.Body as THttpUrlParams; + Result := LURLParamsBody.Items[AIndex].Name; + end; +end; + + +function TMARSDCSRequest.GetFormParams: string; +begin +//AM TODO + Result := ''; +end; + +function TMARSDCSRequest.GetFormParamValue(const AName: string): string; +begin + Result := GetFormParamValue(GetFormParamIndex(AName)); +end; + +function TMARSDCSRequest.GetFormParamValue(const AIndex: Integer): string; +var + LMultiPartBody: THttpMultiPartFormData; + LURLParamsBody: THttpUrlParams; +begin + Result := ''; + if FDCSRequest.BodyType = btMultiPart then + begin + LMultiPartBody := FDCSRequest.Body as THttpMultiPartFormData; + Result := LMultiPartBody.Items[AIndex].AsString; + end + else if FDCSRequest.BodyType = btUrlEncoded then + begin + LURLParamsBody := FDCSRequest.Body as THttpUrlParams; + Result := LURLParamsBody.Items[AIndex].Value; + end; +end; + +function TMARSDCSRequest.GetHeaderParamValue(const AHeaderName: string): string; +begin + Result := ''; + FDCSRequest.Header.GetParamValue(AHeaderName, Result); +end; + +function TMARSDCSRequest.GetHostName: string; +begin + Result := FDCSRequest.HostName; +end; + +function TMARSDCSRequest.GetMethod: string; +begin + Result := FDCSRequest.Method; +end; + +function TMARSDCSRequest.GetPort: Integer; +begin + Result := FDCSRequest.HostPort; +end; + +function TMARSDCSRequest.GetQueryParamCount: Integer; +begin + Result := FDCSRequest.Query.Count; +end; + +function TMARSDCSRequest.GetQueryParamIndex(const AName: string): Integer; +var + LIndex: Integer; +begin + Result := -1; + for LIndex := 0 to FDCSRequest.Query.Count -1 do + begin + if SameText(FDCSRequest.Query.Items[LIndex].Name, AName) then + begin + Result := LIndex; + Break; + end; + end; +end; + +function TMARSDCSRequest.GetQueryParamValue(const AIndex: Integer): string; +begin + Result := FDCSRequest.Query.Items[AIndex].Value; +end; + +function TMARSDCSRequest.GetQueryString: string; +begin +//AM TODO controllare + Result := FDCSRequest.Query.ToString; +end; + +function TMARSDCSRequest.GetRawContent: TBytes; +begin + Result := []; + case FDCSRequest.BodyType of +// btNone: Result := []; +// btUrlEncoded: ; +// btMultiPart: Result := THttpMultiPartFormData(FDCSRequest.Body).Bytes; + btBinary: Result := TBytesStream(FDCSRequest.Body).Bytes; + end; +end; + +function TMARSDCSRequest.GetRawPath: string; +begin +//AM TODO controllare RawPathAndParams? + Result := FDCSRequest.Path; +end; + +{ TMARSDCSResponse } + +constructor TMARSDCSResponse.Create(ADCSResponse: ICrossHttpResponse); +begin + inherited Create; + FDCSResponse := ADCSResponse; +end; + +function TMARSDCSResponse.GetContent: string; +begin +//AM TODO + Result := ''; +end; + +function TMARSDCSResponse.GetContentEncoding: string; +begin +//AM TODO + Result := ''; +end; + +function TMARSDCSResponse.GetContentStream: TStream; +begin +//AM TODO + Result := nil; +end; + +function TMARSDCSResponse.GetContentType: string; +begin + Result := FDCSResponse.ContentType; +end; + +function TMARSDCSResponse.GetStatusCode: Integer; +begin + Result := FDCSResponse.StatusCode; +end; + +procedure TMARSDCSResponse.SetContent(const AContent: string); +begin + FDCSResponse.Send(AContent); +end; + +procedure TMARSDCSResponse.SetContentEncoding(const AContentEncoding: string); +begin +//AM TODO +end; + +procedure TMARSDCSResponse.SetContentStream(const AContentStream: TStream); +begin + FDCSResponse.Send(AContentStream); +end; + +procedure TMARSDCSResponse.SetContentType(const AContentType: string); +begin + FDCSResponse.ContentType := AContentType; +end; + +procedure TMARSDCSResponse.SetCookie(const AName, AValue, ADomain, + APath: string; const AExpiration: TDateTime; const ASecure: Boolean); +begin + FDCSResponse.Cookies.AddOrSet(AName, AValue, SecondsBetween(Now, AExpiration), APath, ADomain, False {AHttpOnly}, ASecure); +end; + +procedure TMARSDCSResponse.SetHeader(const AName, AValue: string); +begin + FDCSResponse.Header.Add(AName, AValue); +end; + +procedure TMARSDCSResponse.SetStatusCode(const AStatusCode: Integer); +begin + FDCSResponse.StatusCode := AStatusCode; +end; + +end. diff --git a/Source/MARS.http.Server.Indy.pas b/Source/MARS.http.Server.Indy.pas index 174fba7f..22a5fd1f 100644 --- a/Source/MARS.http.Server.Indy.pas +++ b/Source/MARS.http.Server.Indy.pas @@ -15,15 +15,76 @@ interface , IdContext, IdCustomHTTPServer, IdException, IdTCPServer, IdIOHandlerSocket , IdSchedulerOfThreadPool , idHTTPWebBrokerBridge - - , MARS.Core.Engine - , MARS.Core.Token + , Web.HttpApp + , MARS.Core.Engine, MARS.Core.Token, MARS.Core.RequestAndResponse.Interfaces ; type TBeforeCommandGetFunc = reference to function (AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo): Boolean; + TMARSWebRequest = class(TInterfacedObject, IMARSRequest) + private + FWebRequest: TWebRequest; + public + // IMARSRequest ------------------------------------------------------------ + function AsObject: TObject; inline; + function GetAccept: string; inline; + function GetAuthorization: string; inline; + function GetContent: string; inline; + function GetCookieParamIndex(const AName: string): Integer; inline; + function GetCookieParamValue(const AIndex: Integer): string; overload; inline; + function GetCookieParamValue(const AName: string): string; overload; inline; + function GetCookieParamCount: Integer; + function GetFilesCount: Integer; inline; + function GetFormParamCount: Integer; inline; + function GetFormParamIndex(const AName: string): Integer; inline; + function GetFormParamName(const AIndex: Integer): string; inline; + function GetFormParamValue(const AIndex: Integer): string; overload; inline; + function GetFormParamValue(const AName: string): string; overload; inline; + function GetFormFileParamIndex(const AName: string): Integer; inline; + function GetFormFileParam(const AIndex: Integer; out AFieldName, AFileName: string; + out ABytes: TBytes; out AContentType: string): Boolean; + function GetFormParams: string; inline; + function GetHeaderParamValue(const AHeaderName: string): string; inline; + function GetHostName: string; inline; + function GetMethod: string; inline; + function GetPort: Integer; inline; + function GetQueryParamIndex(const AName: string): Integer; inline; + function GetQueryParamValue(const AIndex: Integer): string; inline; + function GetQueryParamCount: Integer; + function GetQueryString: string; inline; + function GetRawContent: TBytes; inline; + function GetRawPath: string; inline; + procedure CheckWorkaroundForISAPI; + // ------------------------------------------------------------------------- + constructor Create(AWebRequest: TWebRequest); virtual; + + property WebRequest: TWebRequest read FWebRequest; + end; + + TMARSWebResponse = class(TInterfacedObject, IMARSResponse) + private + FWebResponse: TWebResponse; + public + // IMARSResponse ----------------------------------------------------------- + function GetContent: string; inline; + function GetContentEncoding: string; inline; + function GetContentStream: TStream; inline; + function GetContentType: string; inline; + function GetStatusCode: Integer; inline; + procedure SetContent(const AContent: string); inline; + procedure SetContentEncoding(const AContentEncoding: string); inline; + procedure SetContentStream(const AContentStream: TStream); inline; + procedure SetContentType(const AContentType: string); inline; + procedure SetHeader(const AName: string; const AValue: string); inline; + procedure SetStatusCode(const AStatusCode: Integer); inline; + procedure SetCookie(const AName, AValue, ADomain, APath: string; const AExpiration: TDateTime; const ASecure: Boolean); inline; + // ------------------------------------------------------------------------- + constructor Create(AWebResponse: TWebResponse); virtual; + end; + + TMARShttpServerIndy = class(TIdCustomHTTPServer) private FEngine: TMARSEngine; @@ -59,14 +120,9 @@ implementation uses StrUtils, DateUtils -{$ifdef DelphiXE7_UP} - , Web.HttpApp -{$else} - , HttpApp -{$endif} - , IdCookie - , MARS.Core.Utils - ; +, IdCookie +, MARS.Core.Utils +; { TMARShttpServerIndy } @@ -98,7 +154,7 @@ procedure TMARShttpServerIndy.DoCommandGet(AContext: TIdContext; LResponse.FreeContentStream := False; AResponseInfo.FreeContentStream := True; try - if not FEngine.HandleRequest(LRequest, LResponse) then + if not FEngine.HandleRequest(TMARSWebRequest.Create(LRequest), TMARSWebResponse.Create(LResponse)) then begin LResponse.ContentType := 'application/json'; LResponse.Content := @@ -209,4 +265,252 @@ procedure TMARShttpServerIndy.Startup; inherited; end; +{ TMARSWebRequest } + +function TMARSWebRequest.AsObject: TObject; +begin + Result := Self; +end; + +procedure TMARSWebRequest.CheckWorkaroundForISAPI; +begin + FWebRequest.ReadTotalContent; // workaround for https://quality.embarcadero.com/browse/RSP-14674 +end; + +constructor TMARSWebRequest.Create(AWebRequest: TWebRequest); +begin + inherited Create; + FWebRequest := AWebRequest; +end; + +function TMARSWebRequest.GetAccept: string; +begin + Result := FWebRequest.Accept; +end; + +function TMARSWebRequest.GetAuthorization: string; +begin + Result := FWebRequest.Authorization; +end; + +function TMARSWebRequest.GetContent: string; +begin + Result := FWebRequest.Content; +end; + +function TMARSWebRequest.GetCookieParamCount: Integer; +begin + Result := FWebRequest.CookieFields.Count; +end; + +function TMARSWebRequest.GetCookieParamIndex(const AName: string): Integer; +begin + Result := FWebRequest.CookieFields.IndexOfName(AName); +end; + +function TMARSWebRequest.GetCookieParamValue(const AName: string): string; +begin + Result := FWebRequest.CookieFields.Values[AName]; +end; + +function TMARSWebRequest.GetCookieParamValue(const AIndex: Integer): string; +begin + Result := FWebRequest.CookieFields.ValueFromIndex[AIndex]; +end; + +function TMARSWebRequest.GetFilesCount: Integer; +begin + Result := FWebRequest.Files.Count; +end; + +function TMARSWebRequest.GetFormFileParam(const AIndex: Integer; out AFieldName, + AFileName: string; out ABytes: TBytes; out AContentType: string): Boolean; +var + LFile: TAbstractWebRequestFile; +begin + Result := (AIndex >= 0) and (AIndex < FWebRequest.Files.Count); + if Result then + begin + LFile := FWebRequest.Files[AIndex]; + AFieldName := LFile.FieldName; + AFileName := LFile.FileName; + ABytes := StreamToBytes(LFile.Stream); + AContentType := LFile.ContentType; + end; +end; + +function TMARSWebRequest.GetFormFileParamIndex(const AName: string): Integer; +var + LFile: TAbstractWebRequestFile; + LIndex: Integer; +begin + Result := -1; + for LIndex := 0 to FWebRequest.Files.Count-1 do + begin + LFile := FWebRequest.Files[LIndex]; + if SameText(LFile.FieldName, AName) then + begin + Result := LIndex; + Break; + end; + end; +end; + +function TMARSWebRequest.GetFormParamCount: Integer; +begin + Result := FWebRequest.ContentFields.Count; +end; + +function TMARSWebRequest.GetFormParamIndex(const AName: string): Integer; +begin + Result := FWebRequest.ContentFields.IndexOfName(AName); +end; + +function TMARSWebRequest.GetFormParamName(const AIndex: Integer): string; +begin + Result := FWebRequest.ContentFields.Names[AIndex]; +end; + +function TMARSWebRequest.GetFormParams: string; +begin + Result := FWebRequest.ContentFields.Text; +end; + +function TMARSWebRequest.GetFormParamValue(const AIndex: Integer): string; +begin + Result := FWebRequest.ContentFields.ValueFromIndex[AIndex]; +end; + +function TMARSWebRequest.GetFormParamValue(const AName: string): string; +begin + Result := FWebRequest.ContentFields.Values[AName]; +end; + +function TMARSWebRequest.GetHeaderParamValue(const AHeaderName: string): string; +begin + Result := FWebRequest.GetFieldByName(AHeaderName); +end; + +function TMARSWebRequest.GetHostName: string; +begin + Result := FWebRequest.Host; +end; + +function TMARSWebRequest.GetMethod: string; +begin + Result := FWebRequest.Method; +end; + +function TMARSWebRequest.GetPort: Integer; +begin + Result := FWebRequest.ServerPort; +end; + +function TMARSWebRequest.GetQueryParamCount: Integer; +begin + Result := FWebRequest.QueryFields.Count; +end; + +function TMARSWebRequest.GetQueryParamIndex(const AName: string): Integer; +begin + Result := FWebRequest.QueryFields.IndexOfName(AName); +end; + +function TMARSWebRequest.GetQueryParamValue(const AIndex: Integer): string; +begin + Result := FWebRequest.QueryFields.ValueFromIndex[AIndex]; +end; + +function TMARSWebRequest.GetQueryString: string; +begin + Result := FWebRequest.Query; +end; + +function TMARSWebRequest.GetRawContent: TBytes; +begin + Result := FWebRequest.RawContent; +end; + +function TMARSWebRequest.GetRawPath: string; +begin + Result := FWebRequest.RawPathInfo; +end; + +{ TMARSWebResponse } + +constructor TMARSWebResponse.Create(AWebResponse: TWebResponse); +begin + inherited Create; + FWebResponse := AWebResponse; +end; + +function TMARSWebResponse.GetContent: string; +begin + Result := FWebResponse.Content; +end; + +function TMARSWebResponse.GetContentEncoding: string; +begin + Result := FWebResponse.ContentEncoding; +end; + +function TMARSWebResponse.GetContentStream: TStream; +begin + Result := FWebResponse.ContentStream; +end; + +function TMARSWebResponse.GetContentType: string; +begin + Result := FWebResponse.ContentType; +end; + +function TMARSWebResponse.GetStatusCode: Integer; +begin + Result := FWebResponse.StatusCode; +end; + +procedure TMARSWebResponse.SetContent(const AContent: string); +begin + FWebResponse.Content := AContent; +end; + +procedure TMARSWebResponse.SetContentEncoding(const AContentEncoding: string); +begin + FWebResponse.ContentEncoding := AContentEncoding; +end; + +procedure TMARSWebResponse.SetContentStream(const AContentStream: TStream); +begin + FWebResponse.ContentStream := AContentStream; +end; + +procedure TMARSWebResponse.SetContentType(const AContentType: string); +begin + FWebResponse.ContentType := AContentType; +end; + +procedure TMARSWebResponse.SetCookie(const AName, AValue, ADomain, + APath: string; const AExpiration: TDateTime; const ASecure: Boolean); +var + LSL: TStringList; +begin + LSL := TStringList.Create; + try + LSL.Values[AName] := AValue; + FWebResponse.SetCookieField(LSL, ADomain, APath, AExpiration, ASecure{, AHttpOnly}); + finally + LSL.Free; + end; +end; + +procedure TMARSWebResponse.SetHeader(const AName, AValue: string); +begin + FWebResponse.CustomHeaders.Values[AName] := AValue; +end; + +procedure TMARSWebResponse.SetStatusCode(const AStatusCode: Integer); +begin + FWebResponse.StatusCode := AStatusCode; +end; + end. diff --git a/Source/MARS.inc b/Source/MARS.inc index c5231212..68defed5 100644 --- a/Source/MARS.inc +++ b/Source/MARS.inc @@ -1,7 +1,5 @@ -// *** BEWARE *** -// if your Delphi edition/license does not include FireDAC, -// remove the following MARS_FIREDAC definition! -{$define MARS_FIREDAC} +{$define MARS_FIREDAC} // To enable MARS FireDAC support +{.$define MARS_UNIDAC} // To enable MARS Devart UniDAC support {$if compilerversion = 22} {$define DelphiXE} @@ -51,6 +49,10 @@ {$define Delphi10Rio} {$ENDIF} +{$if compilerversion = 34} + {$define Delphi10Sydney} +{$ENDIF} + {$if compilerversion >= 22} {$define DelphiXE_UP} {$ENDIF} @@ -164,3 +166,19 @@ {$define Delphi10Tokyo_UP} {$define Delphi10Rio_UP} {$ENDIF} + +{$if compilerversion >= 34} + {$define DelphiXE_UP} + {$define DelphiXE2_UP} + {$define DelphiXE3_UP} + {$define DelphiXE4_UP} + {$define DelphiXE5_UP} + {$define DelphiXE6_UP} + {$define DelphiXE7_UP} + {$define DelphiXE8_UP} + {$define Delphi10Seattle_UP} + {$define Delphi10Berlin_UP} + {$define Delphi10Tokyo_UP} + {$define Delphi10Rio_UP} + {$define Delphi10Sydney_UP} +{$ENDIF} diff --git a/ThirdParty/DCS/LICENSE b/ThirdParty/DCS/LICENSE new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/ThirdParty/DCS/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/ThirdParty/DCS/Net/BSD.kqueue.pas b/ThirdParty/DCS/Net/BSD.kqueue.pas new file mode 100644 index 00000000..86813d46 --- /dev/null +++ b/ThirdParty/DCS/Net/BSD.kqueue.pas @@ -0,0 +1,114 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit BSD.kqueue; + +interface + +uses + Posix.Base, Posix.Time; + +const + EVFILT_READ = -1; + EVFILT_WRITE = -2; + EVFILT_AIO = -3; { attached to aio requests } + EVFILT_VNODE = -4; { attached to vnodes } + EVFILT_PROC = -5; { attached to struct proc } + EVFILT_SIGNAL = -6; { attached to struct proc } + EVFILT_TIMER = -7; { timers } + EVFILT_NETDEV = -8; { network devices } + EVFILT_FS = -9; { filesystem events } + + EVFILT_SYSCOUNT = 9; + + EV_ADD = $0001; { add event to kq } + EV_DELETE = $0002; { delete event from kq } + EV_ENABLE = $0004; { enable event } + EV_DISABLE = $0008; { disable event (not reported) } + +{ flags } + EV_ONESHOT = $0010; { only report one occurrence } + EV_CLEAR = $0020; { clear event state after reporting } + EV_RECEIPT = $0040; { force EV_ERROR on success, data=0 } + EV_DISPATCH = $0080; { disable event after reporting } + EV_SYSFLAGS = $F000; { reserved by system } + EV_FLAG1 = $2000; { filter-specific flag } + +{ returned values } + EV_EOF = $8000; { EOF detected } + EV_ERROR = $4000; { error, data contains errno } + +{ data/hint flags for EVFILT_READ|WRITE, shared with userspace } + NOTE_LOWAT = $0001; { low water mark } + +{ data/hint flags for EVFILT_VNODE, shared with userspace } + NOTE_DELETE = $0001; { vnode was removed } + NOTE_WRITE = $0002; { data contents changed } + NOTE_EXTEND = $0004; { size increased } + NOTE_ATTRIB = $0008; { attributes changed } + NOTE_LINK = $0010; { link count changed } + NOTE_RENAME = $0020; { vnode was renamed } + NOTE_REVOKE = $0040; { vnode access was revoked } + +{ data/hint flags for EVFILT_PROC, shared with userspace } + NOTE_EXIT = $80000000; { process exited } + NOTE_FORK = $40000000; { process forked } + NOTE_EXEC = $20000000; { process exec'd } + NOTE_PCTRLMASK = $f0000000; { mask for hint bits } + NOTE_PDATAMASK = $000fffff; { mask for pid } + +{ additional flags for EVFILT_PROC } + NOTE_TRACK = $00000001; { follow across forks } + NOTE_TRACKERR = $00000002; { could not track child } + NOTE_CHILD = $00000004; { am a child process } + +{ data/hint flags for EVFILT_NETDEV, shared with userspace } + NOTE_LINKUP = $0001; { link is up } + NOTE_LINKDOWN = $0002; { link is down } + NOTE_LINKINV = $0004; { link state is invalid } + +type + PKEvent = ^TKEvent; + TKEvent = record + Ident : UIntPtr; { identifier for this event } + Filter : SmallInt; { filter for event } + Flags : Word; + FFlags : Cardinal; + Data : IntPtr; + uData : Pointer; { opaque user data identifier } + end; + +function kqueue: Integer; cdecl; + external libc name _PU + 'kqueue'; + {$EXTERNALSYM kqueue} + +function kevent(kq: Integer; ChangeList: PKEvent; nChanged: Integer; + EventList: PKevent; nEvents: Integer; Timeout: PTimeSpec): Integer; cdecl; + external libc name _PU + 'kevent'; + {$EXTERNALSYM kevent} + +procedure EV_SET(kevp: PKEvent; const aIdent: UIntPtr; const aFilter: SmallInt; + const aFlags: Word; const aFFlags: Cardinal; const aData: IntPtr; + const auData: Pointer); inline; + +implementation + +procedure EV_SET(kevp: PKEvent; const aIdent: UIntPtr; const aFilter: SmallInt; + const aFlags: Word; const aFFlags: Cardinal; const aData: IntPtr; + const auData: Pointer); inline; +begin + kevp^.Ident := aIdent; + kevp^.Filter := aFilter; + kevp^.Flags := aFlags; + kevp^.FFlags := aFFlags; + kevp^.Data := aData; + kevp^.uData := auData; +end; + +end. diff --git a/ThirdParty/DCS/Net/Linux.epoll.pas b/ThirdParty/DCS/Net/Linux.epoll.pas new file mode 100644 index 00000000..3ef74f62 --- /dev/null +++ b/ThirdParty/DCS/Net/Linux.epoll.pas @@ -0,0 +1,72 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Linux.epoll; + +interface + +uses + Posix.Base, Posix.Signal; + +const + EPOLLIN = $01; { The associated file is available for read(2) operations. } + EPOLLPRI = $02; { There is urgent data available for read(2) operations. } + EPOLLOUT = $04; { The associated file is available for write(2) operations. } + EPOLLERR = $08; { Error condition happened on the associated file descriptor. } + EPOLLHUP = $10; { Hang up happened on the associated file descriptor. } + EPOLLONESHOT = $40000000; { Sets the One-Shot behaviour for the associated file descriptor. } + EPOLLET = $80000000; { Sets the Edge Triggered behaviour for the associated file descriptor. } + + { Valid opcodes ( "op" parameter ) to issue to epoll_ctl } + EPOLL_CTL_ADD = 1; + EPOLL_CTL_DEL = 2; + EPOLL_CTL_MOD = 3; + +type + EPoll_Data = record + case integer of + 0: (ptr: pointer); + 1: (fd: Integer); + 2: (u32: Cardinal); + 3: (u64: UInt64); + end; + TEPoll_Data = Epoll_Data; + PEPoll_Data = ^Epoll_Data; + + EPoll_Event = {$IFDEF CPUX64}packed {$ENDIF}record + Events: Cardinal; + Data : TEpoll_Data; + end; + + TEPoll_Event = Epoll_Event; + PEpoll_Event = ^Epoll_Event; + +{ open an epoll file descriptor } +function epoll_create(size: Integer): Integer; cdecl; + external libc name _PU + 'epoll_create'; + {$EXTERNALSYM epoll_create} + +{ control interface for an epoll descriptor } +function epoll_ctl(epfd, op, fd: Integer; event: pepoll_event): Integer; cdecl; + external libc name _PU + 'epoll_ctl'; + {$EXTERNALSYM epoll_ctl} + +{ wait for an I/O event on an epoll file descriptor } +function epoll_wait(epfd: Integer; events: pepoll_event; maxevents, timeout: Integer): Integer; cdecl; + external libc name _PU + 'epoll_wait'; + {$EXTERNALSYM epoll_wait} + +{ create a file descriptor for event notification } +function eventfd(initval: Cardinal; flags: Integer): Integer; cdecl; + external libc name _PU + 'eventfd'; + {$EXTERNALSYM eventfd} + +implementation + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossHttpMiddleware.pas b/ThirdParty/DCS/Net/Net.CrossHttpMiddleware.pas new file mode 100644 index 00000000..810d8398 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossHttpMiddleware.pas @@ -0,0 +1,222 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossHttpMiddleware; + +interface + +uses + System.SysUtils, Net.CrossHttpServer; + +type + /// + /// HTTP֤ȡû + /// + TAuthGetPasswordProc = reference to procedure(ARequest: ICrossHttpRequest; const AUserName: string; var ACorrectPassword: string); + + /// + /// м + /// + /// + /// TCrossHttpServer.Use() + /// + TNetCrossMiddleware = class + public + /// + /// HTTP֤ + /// + /// + /// û֤ + /// + /// + /// + /// άٿ: HTTP֤ + /// + class function AuthenticateBasic(AAuthGetPasswordProc: TAuthGetPasswordProc; const ARealm: string = ''): TCrossHttpRouterProc2; static; + + /// + /// HTTPժҪ֤ + /// + /// + /// û֤ + /// + /// + /// + /// άٿ: HTTPժҪ֤ + /// + class function AuthenticateDigest(AAuthGetPasswordProc: TAuthGetPasswordProc; const ARealm: string = ''): TCrossHttpRouterProc2; static; + + /// + /// ԴԴ + /// + /// + /// + /// άٿ: ԴԴ + /// + class function CORS: TCrossHttpRouterProc2; static; + + /// + /// HTTPϸ䰲ȫ + /// + /// + /// + /// άٿ: HTTPϸ䰲ȫ + /// + class function HSTS: TCrossHttpRouterProc2; static; + end; + +implementation + +uses + System.Hash, System.NetEncoding, Utils.Utils, Net.CrossHttpParams; + +{ TNetCrossMiddleware } + +class function TNetCrossMiddleware.AuthenticateBasic(AAuthGetPasswordProc: TAuthGetPasswordProc; + const ARealm: string): TCrossHttpRouterProc2; +begin + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + var + LAuthStr: string; + LStrArr: TArray; + LCorrectPassword: string; + begin + // Authorization: Basic cm9vdDpyb290 + // base64ֽʽΪ "û:" + LAuthStr := ARequest.Header['Authorization']; + if (LAuthStr <> '') then + begin + if (LAuthStr.StartsWith('Basic')) then + LAuthStr := LAuthStr.Substring(6) + else + LAuthStr := ''; + end; + + LCorrectPassword := #0; + if (LAuthStr <> '') then + begin + LAuthStr := TNetEncoding.Base64.Decode(LAuthStr); + LStrArr := LAuthStr.Split([':']); + + // ȡûӦȷ + if Assigned(AAuthGetPasswordProc) and (Length(LStrArr) > 0) then + AAuthGetPasswordProc(ARequest, LStrArr[0], LCorrectPassword); + end; + + // ƥ + if (LAuthStr = '') or (Length(LStrArr) < 2) or (LStrArr[1] <> LCorrectPassword) then + begin + AHandled := True; + AResponse.Header['WWW-authenticate'] := Format('Basic Realm="%s"', [ARealm]); + AResponse.SendStatus(401); + Exit; + end; + + AHandled := False; + end; +end; + +class function TNetCrossMiddleware.AuthenticateDigest( + AAuthGetPasswordProc: TAuthGetPasswordProc; const ARealm: string): TCrossHttpRouterProc2; +begin + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + var + LUserName, LCorrectPassword: string; + LNonce, LUserResponse, LCorrectResponse: string; + LAuthStr: string; + A1, A2, HA1, HA2: string; + LAuthParams: TDelimitParams; + begin + // Authorization: Digest username="admin", realm="test realm", nonce="2468217498b46028705d401192459edd", uri="/login?key=value1", response="1d663058353e8f5831328728c29a6a1a", qop=auth, nc=00000006, cnonce="5d63a594e16feba2" + LAuthStr := ARequest.Header['Authorization']; + if (LAuthStr <> '') then + begin + if (LAuthStr.StartsWith('Digest')) then + LAuthStr := LAuthStr.Substring(7) + else + LAuthStr := ''; + end; + + LCorrectPassword := #0; + if (LAuthStr <> '') then + begin + LAuthParams := TDelimitParams.Create; + try + LAuthParams.Delimiter := ','; + LAuthParams.Decode(LAuthStr); + + LUserName := LAuthParams['username'].Replace('"', ''); + // ȡûӦȷ + if Assigned(AAuthGetPasswordProc) then + AAuthGetPasswordProc(ARequest, LUserName, LCorrectPassword); + + {$region 'ժҪ'} + A1 := Format('%s:%s:%s', [LUserName, ARealm, LCorrectPassword]); + A2 := Format('%s:%s', [ARequest.Method, LAuthParams['uri'].Replace('"', '')]); + + HA1 := TUtils.BytesToHex(THashMD5.GetHashBytes(A1)); + HA2 := TUtils.BytesToHex(THashMD5.GetHashBytes(A2)); + + LCorrectResponse := HA1 + + ':' + LAuthParams['nonce'].Replace('"', '') + + ':' + LAuthParams['nc'].Replace('"', '') + + ':' + LAuthParams['cnonce'].Replace('"', '') + + ':auth' + + ':' + HA2; + LCorrectResponse := TUtils.BytesToHex(THashMD5.GetHashBytes(LCorrectResponse)); + {$endregion} + + // ͻѼõժҪ + LUserResponse := LAuthParams['response'].Replace('"', ''); + finally + FreeAndNil(LAuthParams); + end; + end; + + // ȶԿͻ˵ժҪǷƥ + if (LAuthStr = '') or (LUserResponse <> LCorrectResponse) then + begin + AHandled := True; + LNonce := TUtils.BytesToHex(THashMD5.GetHashBytes(DateTimeToStr(Now))); + AResponse.Header['WWW-authenticate'] := Format( + 'Digest realm="%s", qop=auth, nonce="%s"', + [ARealm, LNonce]); + AResponse.SendStatus(401); + Exit; + end; + + AHandled := False; + end; +end; + +class function TNetCrossMiddleware.CORS: TCrossHttpRouterProc2; +begin + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + begin + AHandled := False; + AResponse.Header['Access-Control-Allow-Origin'] := '*'; + AResponse.Header['Access-Control-Allow-Methods'] := '*'; + AResponse.Header['Access-Control-Allow-Headers'] := '*'; + end; +end; + +class function TNetCrossMiddleware.HSTS: TCrossHttpRouterProc2; +begin + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + begin + AHandled := False; + AResponse.Header['Strict-Transport-Security'] := 'max-age=31536000; includeSubDomains'; + end; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossHttpParams.pas b/ThirdParty/DCS/Net/Net.CrossHttpParams.pas new file mode 100644 index 00000000..42b03209 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossHttpParams.pas @@ -0,0 +1,2005 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossHttpParams; + +interface + +uses + System.SysUtils, + System.Classes, + System.Generics.Collections, + System.Generics.Defaults, + System.NetEncoding, + System.IOUtils, + System.RegularExpressions, + System.SyncObjs, + System.Diagnostics, + System.DateUtils, + Net.CrossHttpUtils; + +type + TNameValue = record + Name, Value: string; + constructor Create(const AName, AValue: string); + end; + + /// + /// + /// + TBaseParams = class(TEnumerable) + private + FParams: TList; + + function GetParamIndex(const AName: string): Integer; + function GetParam(const AName: string): string; + procedure SetParam(const AName, AValue: string); + function GetCount: Integer; + function GetItem(AIndex: Integer): TNameValue; + procedure SetItem(AIndex: Integer; const AValue: TNameValue); + protected + function DoGetEnumerator: TEnumerator; override; + public type + TEnumerator = class(TEnumerator) + private + FList: TList; + FIndex: Integer; + protected + function DoGetCurrent: TNameValue; override; + function DoMoveNext: Boolean; override; + public + constructor Create(const AList: TList); + end; + public + constructor Create; overload; virtual; + constructor Create(const AEncodedParams: string); overload; virtual; + destructor Destroy; override; + + /// + /// Ӳ + /// + /// + /// + /// + /// + /// ֵ + /// + /// + /// Ƿ + /// + procedure Add(const AName, AValue: string; ADupAllowed: Boolean = False); overload; + + /// + /// ѱ + /// + /// + /// ѱַ + /// + procedure Add(const AEncodedParams: string); overload; + + /// + /// ɾָ + /// + /// + /// + /// + procedure Remove(const AName: string); overload; + + /// + /// ɾָ + /// + /// + /// + /// + procedure Remove(AIndex: Integer); overload; + + /// + /// в + /// + procedure Clear; + + /// + /// Բ + /// + procedure Sort(const AComparison: TComparison = nil); + + /// + /// ѱַн + /// + /// + /// ѱַ + /// + /// + /// Ƿ + /// + procedure Decode(const AEncodedParams: string; AClear: Boolean = True); virtual; abstract; + + /// + /// Ϊַ + /// + function Encode: string; virtual; abstract; + + /// + /// ȡֵ + /// + function GetParamValue(const AName: string; out AValue: string): Boolean; + + /// + /// Ʒʲ + /// + property Params[const AName: string]: string read GetParam write SetParam; default; + + /// + /// ŷʲ + /// + property Items[AIndex: Integer]: TNameValue read GetItem write SetItem; + + /// + /// + /// + property Count: Integer read GetCount; + end; + + /// + /// Url + /// + THttpUrlParams = class(TBaseParams) + private + FEncodeName: Boolean; + FEncodeValue: Boolean; + public + constructor Create; override; + + /// + /// ѱַн + /// + /// + /// ѱַ + /// + /// + /// Ƿ + /// + procedure Decode(const AEncodedParams: string; AClear: Boolean = True); override; + + /// + /// Ϊַ + /// + function Encode: string; override; + + /// + /// Ƿ + /// + property EncodeName: Boolean read FEncodeName write FEncodeName; + + /// + /// Ƿ + /// + property EncodeValue: Boolean read FEncodeValue write FEncodeValue; + end; + + /// + /// HTTPͷ + /// + THttpHeader = class(TBaseParams) + public + /// + /// ѱַн + /// + /// + /// ѱַ + /// + /// + /// Ƿ + /// + procedure Decode(const AEncodedParams: string; AClear: Boolean = True); override; + + /// + /// Ϊַ + /// + function Encode: string; override; + end; + + /// + /// ָIJ + /// + TDelimitParams = class(TBaseParams) + private + FDelimiter: Char; + public + /// + /// ѱַн + /// + /// + /// ѱַ + /// + /// + /// Ƿ + /// + procedure Decode(const AEncodedParams: string; AClear: Boolean = True); override; + + /// + /// Ϊַ + /// + function Encode: string; override; + + /// + /// ַָ + /// + property Delimiter: Char read FDelimiter write FDelimiter; + end; + + /// + /// ͻͷеCookies + /// + TRequestCookies = class(TBaseParams) + public + /// + /// ѱַн + /// + /// + /// ѱַ + /// + /// + /// Ƿ + /// + procedure Decode(const AEncodedParams: string; AClear: Boolean = True); override; + + /// + /// Ϊַ + /// + function Encode: string; override; + end; + + TResponseCookie = record + /// + /// Cookie + /// + Name: string; + + /// + /// Cookie + /// + Value: string; + + /// + /// CookieЧ, Ϊ0رպCookieʧЧ + /// + MaxAge: Integer; + + /// + /// + /// + /// + /// CookieЧ, ֻе·ͬʱʱ, ŻὫCookie͸Server. + /// ûDomainPathĻ, ǻᱻĬΪǰҳӦֵ + /// + Domain: string; + + /// + /// · + /// + /// + /// CookieЧ, ֻе·ͬʱʱ, ŻὫCookie͸Server. + /// ûDomainPathĻ, ǻᱻĬΪǰҳӦֵ + /// + Path: string; + + /// + /// Ƿ HttpOnly + /// + /// + /// HttpOnlyֶθ, ֻHTTPЭʹ, Ľűɼ, ԿվűʱҲᱻȡ + /// + HttpOnly: Boolean; + + /// + /// ǷSecure + /// + /// + /// Secureֶθhttpsͨʱ, Cookieаȫ, ʱкڿͼҲ޷ȡcookie + /// + Secure: Boolean; + + constructor Create(const AName, AValue: string; AMaxAge: Integer; + const APath: string = ''; const ADomain: string = ''; + AHttpOnly: Boolean = False; ASecure: Boolean = False); + + function Encode: string; + end; + + /// + /// Cookie + /// + TResponseCookies = class(TList) + private + function GetCookieIndex(const AName: string): Integer; + function GetCookie(const AName: string): TResponseCookie; + procedure SetCookie(const AName: string; const Value: TResponseCookie); + public + procedure AddOrSet(const AName, AValue: string; AMaxAge: Integer; + const APath: string = ''; const ADomain: string = ''; + AHttpOnly: Boolean = False; ASecure: Boolean = False); + procedure Remove(const AName: string); + + property Cookies[const AName: string]: TResponseCookie read GetCookie write SetCookie; + end; + + TFormField = class + private + FName: string; + FValue: TStream; + FFileName: string; + FFilePath: string; + FContentType: string; + FContentTransferEncoding: string; + public + constructor Create; + destructor Destroy; override; + + /// + /// תΪֽ + /// + function AsBytes: TBytes; + + /// + /// תΪַ + /// + /// + /// ַ + /// + function AsString(AEncoding: TEncoding = nil): string; + + /// + /// ͷ + /// + procedure FreeValue; + + /// + /// + /// + property Name: string read FName; + + /// + /// ԭʼ + /// + property Value: TStream read FValue; + + /// + /// ļֻļиԣ + /// + property FileName: string read FFileName; + + /// + /// ļ·ֻļиԣ + /// + property FilePath: string read FFilePath; + + /// + /// ֻͣļиԣ + /// + property ContentType: string read FContentType; + property ContentTransferEncoding: string read FContentTransferEncoding; + end; + + /// + /// MultiPartFormData + /// + THttpMultiPartFormData = class(TEnumerable) + public type + TDecodeState = (dsBoundary, dsDetect, dsPartHeader, dsPartData); + private const + DETECT_HEADER_BYTES: array [0..1] of Byte = (13, 10); // س + DETECT_END_BYTES: array [0..3] of Byte = (45, 45, 13, 10); // --س + MAX_PART_HEADER: Integer = 64 * 1024; + private + FBoundary, FStoragePath: string; + FBoundaryBytes, FLookbehind: TBytes; + FBoundaryIndex, FDetectHeaderIndex, FDetectEndIndex, FPartDataBegin: Integer; + FPrevIndex: Integer; + FDecodeState: TDecodeState; + CR, LF: Integer; + FPartFields: TObjectList; + FCurrentPartHeader: TBytesStream; + FCurrentPartField: TFormField; + FAutoDeleteFiles: Boolean; + + function GetItemIndex(const AName: string): Integer; + function GetItem(AIndex: Integer): TFormField; + function GetCount: Integer; + function GetDataSize: Integer; + function GetField(const AName: string): TFormField; + protected + function DoGetEnumerator: TEnumerator; override; + public type + TEnumerator = class(TEnumerator) + private + FList: TList; + FIndex: Integer; + protected + function DoGetCurrent: TFormField; override; + function DoMoveNext: Boolean; override; + public + constructor Create(const AList: TList); + end; + public + constructor Create; virtual; + destructor Destroy; override; + + /// + /// ʼBoundary(Decode֮ǰ) + /// + procedure InitWithBoundary(const ABoundary: string); + + /// + /// ڴн(ȵInitWithBoundary) + /// + /// + /// + /// + /// + /// ݳ + /// + function Decode(const ABuf: Pointer; ALen: Integer): Integer; + + /// + /// Items + /// + procedure Clear; + + /// + /// Boundaryַ(ֻ) + /// + property Boundary: string read FBoundary; + + /// + /// ϴļ· + /// + property StoragePath: string read FStoragePath write FStoragePath; + + /// + /// ŷʲ + /// + property Items[AIndex: Integer]: TFormField read GetItem; + + /// + /// Ʒʲ + /// + property Fields[const AName: string]: TFormField read GetField; + + /// + /// Items(ֻ) + /// + property Count: Integer read GetCount; + + /// + /// Itemsݵܳߴ(ֽ) + /// + property DataSize: Integer read GetDataSize; + + /// + /// ͷʱԶɾϴļ + /// + property AutoDeleteFiles: Boolean read FAutoDeleteFiles write FAutoDeleteFiles; + end; + + /// + /// SessionԱӿ + /// + ISession = interface + ['{A3D525A1-C534-4CE6-969B-53C5B8CB77C3}'] + function GetSessionID: string; + function GetCreateTime: TDateTime; + function GetLastAccessTime: TDateTime; + function GetExpiryTime: Integer; + function GetValue(const AName: string): string; + procedure SetSessionID(const ASessionID: string); + procedure SetCreateTime(const ACreateTime: TDateTime); + procedure SetLastAccessTime(const ALastAccessTime: TDateTime); + procedure SetExpiryTime(const Value: Integer); + procedure SetValue(const AName, AValue: string); + + /// + /// ʱ + /// + procedure Touch; + + /// + /// Ƿѹ + /// + function Expired: Boolean; + + /// + /// Session ID + /// + property SessionID: string read GetSessionID write SetSessionID; + + /// + /// ʱ + /// + property CreateTime: TDateTime read GetCreateTime write SetCreateTime; + + /// + /// ʱ + /// + property LastAccessTime: TDateTime read GetLastAccessTime write SetLastAccessTime; + + /// + /// Sessionʱ() + /// + /// + /// + /// + /// ֵ0ʱ, Session趨ֵûʹþͻᱻͷ; + /// + /// + /// ֵСڵ0ʱ, SessionɺһֱЧ + /// + /// + /// + property ExpiryTime: Integer read GetExpiryTime write SetExpiryTime; + + /// + /// SessionһKEY-VALUEṹ, ڷеijԱֵ + /// + property Values[const AName: string]: string read GetValue write SetValue; default; + end; + + TSessionBase = class abstract(TInterfacedObject, ISession) + protected + function GetSessionID: string; virtual; abstract; + function GetCreateTime: TDateTime; virtual; abstract; + function GetLastAccessTime: TDateTime; virtual; abstract; + function GetExpiryTime: Integer; virtual; abstract; + function GetValue(const AName: string): string; virtual; abstract; + procedure SetSessionID(const ASessionID: string); virtual; abstract; + procedure SetCreateTime(const ACreateTime: TDateTime); virtual; abstract; + procedure SetLastAccessTime(const ALastAccessTime: TDateTime); virtual; abstract; + procedure SetExpiryTime(const Value: Integer); virtual; abstract; + procedure SetValue(const AName, AValue: string); virtual; abstract; + public + constructor Create(const ASessionID: string); virtual; + + procedure Touch; virtual; + function Expired: Boolean; virtual; + + property SessionID: string read GetSessionID write SetSessionID; + property CreateTime: TDateTime read GetCreateTime write SetCreateTime; + property LastAccessTime: TDateTime read GetLastAccessTime write SetLastAccessTime; + property ExpiryTime: Integer read GetExpiryTime write SetExpiryTime; + property Values[const AName: string]: string read GetValue write SetValue; default; + end; + + TSession = class(TSessionBase) + protected + FSessionID: string; + FCreateTime: TDateTime; + FLastAccessTime: TDateTime; + FExpire: Integer; + FValues: TDictionary; + + function GetSessionID: string; override; + function GetCreateTime: TDateTime; override; + function GetLastAccessTime: TDateTime; override; + function GetExpiryTime: Integer; override; + function GetValue(const AName: string): string; override; + procedure SetSessionID(const ASessionID: string); override; + procedure SetCreateTime(const ACreateTime: TDateTime); override; + procedure SetLastAccessTime(const ALastAccessTime: TDateTime); override; + procedure SetExpiryTime(const AValue: Integer); override; + procedure SetValue(const AName, AValue: string); override; + public + constructor Create(const ASessionID: string); override; + destructor Destroy; override; + + property SessionID: string read GetSessionID write SetSessionID; + property CreateTime: TDateTime read GetCreateTime write SetCreateTime; + property LastAccessTime: TDateTime read GetLastAccessTime write SetLastAccessTime; + property Values[const AName: string]: string read GetValue write SetValue; default; + end; + + TSessionClass = class of TSessionBase; + + /// + /// Sessionӿ + /// + ISessions = interface + ['{5187CA76-4CC4-4986-B67B-BC3E76D6CD74}'] + function GetEnumerator: TEnumerator; + + function GetSessionClass: TSessionClass; + function GetCount: Integer; + function GetItem(const AIndex: Integer): ISession; + function GetSession(const ASessionID: string): ISession; + function GetExpiryTime: Integer; + procedure SetSessionClass(const Value: TSessionClass); + procedure SetExpiryTime(const Value: Integer); + + /// + /// ʼд(߳ͬ) + /// + procedure BeginWrite; + + /// + /// д(߳ͬ) + /// + procedure EndWrite; + + /// + /// ʼ(߳ͬ) + /// + procedure BeginRead; + + /// + /// (߳ͬ) + /// + procedure EndRead; + + /// + /// Session ID + /// + function NewSessionID: string; + + /// + /// ǷָIDSession + /// + /// + /// Session ID + /// + /// + /// ָSession ʵ浽ò + /// + function ExistsSession(const ASessionID: string; var ASession: ISession): Boolean; overload; + + /// + /// ǷָIDSession + /// + /// + /// Session ID + /// + function ExistsSession(const ASessionID: string): Boolean; overload; + + /// + /// Session + /// + /// + /// Session ID + /// + /// + /// Sessionʵ + /// + function AddSession(const ASessionID: string): ISession; overload; + + /// + /// Session + /// + /// + /// Sessionʵ + /// + function AddSession: ISession; overload; + + /// + /// Session + /// + /// + /// Session ID + /// + /// + /// Sessionʵ + /// + procedure AddSession(const ASessionID: string; ASession: ISession); overload; + + /// + /// ɾSession + /// + /// + /// Session ID + /// + procedure RemoveSession(const ASessionID: string); + + /// + /// Session + /// + property SessionClass: TSessionClass read GetSessionClass write SetSessionClass; + + /// + /// Session + /// + property Count: Integer read GetCount; + + /// + /// ȡָŵSession, 򷵻nil + /// + property Items[const AIndex: Integer]: ISession read GetItem; + + /// + /// ȡָIDSession, ½һ + /// + /// + /// Session ID + /// + property Sessions[const ASessionID: string]: ISession read GetSession; default; + + /// + /// Sessionʱ() + /// + /// + /// + /// + /// ֵ0ʱ, Session趨ֵûʹþͻᱻͷ; + /// + /// + /// ֵСڵ0ʱ, SessionɺһֱЧ + /// + /// + /// + property ExpiryTime: Integer read GetExpiryTime write SetExpiryTime; + end; + + TSessionsBase = class abstract(TInterfacedObject, ISessions) + protected + function GetSessionClass: TSessionClass; virtual; abstract; + function GetCount: Integer; virtual; abstract; + function GetItem(const AIndex: Integer): ISession; virtual; abstract; + function GetSession(const ASessionID: string): ISession; virtual; abstract; + function GetExpiryTime: Integer; virtual; abstract; + procedure SetSessionClass(const Value: TSessionClass); virtual; abstract; + procedure SetExpiryTime(const Value: Integer); virtual; abstract; + public + function GetEnumerator: TEnumerator; virtual; abstract; + + procedure BeginWrite; virtual; abstract; + procedure EndWrite; virtual; abstract; + + procedure BeginRead; virtual; abstract; + procedure EndRead; virtual; abstract; + + function NewSessionID: string; virtual; abstract; + function ExistsSession(const ASessionID: string; var ASession: ISession): Boolean; overload; virtual; abstract; + function ExistsSession(const ASessionID: string): Boolean; overload; virtual; + function AddSession(const ASessionID: string): ISession; overload; virtual; + function AddSession: ISession; overload; virtual; + procedure AddSession(const ASessionID: string; ASession: ISession); overload; virtual; abstract; + procedure RemoveSession(const ASessionID: string); virtual; abstract; + + property SessionClass: TSessionClass read GetSessionClass write SetSessionClass; + property Count: Integer read GetCount; + property Items[const AIndex: Integer]: ISession read GetItem; + property Sessions[const ASessionID: string]: ISession read GetSession; default; + property ExpiryTime: Integer read GetExpiryTime write SetExpiryTime; + end; + + TSessions = class(TSessionsBase) + private + FSessions: TDictionary; + FNewGUIDFunc: TFunc; + FLocker: TMultiReadExclusiveWriteSynchronizer; + FSessionClass: TSessionClass; + FExpire: Integer; + FShutdown, FExpiredProcRunning: Boolean; + protected + function GetSessionClass: TSessionClass; override; + function GetCount: Integer; override; + function GetItem(const AIndex: Integer): ISession; override; + function GetSession(const ASessionID: string): ISession; override; + function GetExpiryTime: Integer; override; + procedure SetSessionClass(const Value: TSessionClass); override; + procedure SetExpiryTime(const Value: Integer); override; + + procedure CreateExpiredProcThread; + public + constructor Create(ANewGUIDFunc: TFunc); overload; virtual; + constructor Create; overload; virtual; + destructor Destroy; override; + + function GetEnumerator: TEnumerator; override; + + procedure BeginWrite; override; + procedure EndWrite; override; + + procedure BeginRead; override; + procedure EndRead; override; + + function NewSessionID: string; override; + function ExistsSession(const ASessionID: string; var ASession: ISession): Boolean; override; + procedure AddSession(const ASessionID: string; ASession: ISession); override; + procedure RemoveSession(const ASessionID: string); override; + + property NewGUIDFunc: TFunc read FNewGUIDFunc write FNewGUIDFunc; + end; + +implementation + +uses + Utils.Utils, + Utils.DateTime; + +{ TNameValue } + +constructor TNameValue.Create(const AName, + AValue: string); +begin + Name := AName; + Value := AValue; +end; + +{ TBaseParams.TEnumerator } + +constructor TBaseParams.TEnumerator.Create(const AList: TList); +begin + inherited Create; + FList := AList; + FIndex := -1; +end; + +function TBaseParams.TEnumerator.DoGetCurrent: TNameValue; +begin + Result := FList[FIndex]; +end; + +function TBaseParams.TEnumerator.DoMoveNext: Boolean; +begin + if (FIndex >= FList.Count) then + Exit(False); + Inc(FIndex); + Result := (FIndex < FList.Count); +end; + +{ TBaseParams } + +constructor TBaseParams.Create; +begin + FParams := TList.Create(TComparer.Construct( + function(const Left, Right: TNameValue): Integer + begin + Result := CompareText(Left.Name, Right.Name, TLocaleOptions.loUserLocale); + end)); +end; + +constructor TBaseParams.Create(const AEncodedParams: string); +begin + Create; + Decode(AEncodedParams, True); +end; + +destructor TBaseParams.Destroy; +begin + FreeAndNil(FParams); + inherited; +end; + +procedure TBaseParams.Add(const AName, AValue: string; ADupAllowed: Boolean); +begin + if ADupAllowed then + FParams.Add(TNameValue.Create(AName, AValue)) + else + SetParam(AName, AValue); +end; + +procedure TBaseParams.Add(const AEncodedParams: string); +begin + Decode(AEncodedParams, False); +end; + +procedure TBaseParams.Clear; +begin + FParams.Clear; +end; + +function TBaseParams.GetParamIndex(const AName: string): Integer; +var + I: Integer; +begin + for I := 0 to FParams.Count - 1 do + if SameText(FParams[I].Name, AName) then Exit(I); + Result := -1; +end; + +function TBaseParams.GetParamValue(const AName: string; + out AValue: string): Boolean; +var + I: Integer; +begin + I := GetParamIndex(AName); + if (I >= 0) then + begin + AValue := FParams[I].Value; + Exit(True); + end; + + Result := False; +end; + +procedure TBaseParams.Remove(const AName: string); +var + I: Integer; +begin + I := GetParamIndex(AName); + if (I >= 0) then + FParams.Delete(I); +end; + +procedure TBaseParams.Remove(AIndex: Integer); +begin + FParams.Delete(AIndex); +end; + +function TBaseParams.GetCount: Integer; +begin + Result := FParams.Count; +end; + +function TBaseParams.GetItem(AIndex: Integer): TNameValue; +begin + Result := FParams.Items[AIndex]; +end; + +function TBaseParams.DoGetEnumerator: TEnumerator; +begin + Result := TEnumerator.Create(FParams); +end; + +function TBaseParams.GetParam(const AName: string): string; +var + I: Integer; +begin + I := GetParamIndex(AName); + if (I >= 0) then + Exit(FParams[I].Value); + Result := ''; +end; + +procedure TBaseParams.SetItem(AIndex: Integer; const AValue: TNameValue); +begin + FParams[AIndex] := AValue; +end; + +procedure TBaseParams.SetParam(const AName, AValue: string); +var + I: Integer; + LItem: TNameValue; +begin + I := GetParamIndex(AName); + if (I >= 0) then + begin + LItem := FParams[I]; + LItem.Value := AValue; + FParams[I] := LItem; + end else + FParams.Add(TNameValue.Create(AName, AValue)); +end; + +procedure TBaseParams.Sort(const AComparison: TComparison); +begin + if Assigned(AComparison) then + FParams.Sort(TComparer.Construct(AComparison)) + else + FParams.Sort(TComparer.Construct( + function(const Left, Right: TNameValue): Integer + begin + Result := CompareStr(Left.Name, Right.Name, TLocaleOptions.loInvariantLocale); + end)); +end; + +{ THttpUrlParams } + +constructor THttpUrlParams.Create; +begin + inherited Create; + + FEncodeName := False; + FEncodeValue := True; +end; + +procedure THttpUrlParams.Decode(const AEncodedParams: string; AClear: Boolean); +var + p, q: PChar; + LName, LValue: string; + LSize: Integer; +begin + if AClear then + FParams.Clear; + + p := PChar(AEncodedParams); + while (p^ <> #0) do + begin + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> '=') do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LName, LSize); + Move(q^, Pointer(LName)^, LSize * SizeOf(Char)); + LName := TNetEncoding.URL.Decode(LName); + // '=' + while (p^ <> #0) and (p^ = '=') do + Inc(p); + + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> '&') do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LValue, LSize); + Move(q^, Pointer(LValue)^, LSize * SizeOf(Char)); + LValue := TNetEncoding.URL.Decode(LValue); + // '&' + while (p^ <> #0) and (p^ = '&') do + Inc(p); + + Add(LName, LValue); + end; +end; + +function THttpUrlParams.Encode: string; +var + I: Integer; +begin + Result := ''; + for I := 0 to FParams.Count - 1 do + begin + if (I > 0) then + Result := Result + '&'; + if FEncodeName then + Result := Result + TNetEncoding.URL.Encode(FParams[I].Name) + else + Result := Result + FParams[I].Name; + if FEncodeValue then + Result := Result + '=' + TNetEncoding.URL.Encode(FParams[I].Value) + else + Result := Result + '=' + FParams[I].Value; + end; +end; + +{ THttpHeader } + +procedure THttpHeader.Decode(const AEncodedParams: string; AClear: Boolean); +var + p, q: PChar; + LName, LValue: string; + LSize: Integer; +begin + if AClear then + FParams.Clear; + + p := PChar(AEncodedParams); + while (p^ <> #0) do + begin + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> ':') do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LName, LSize); + Move(q^, Pointer(LName)^, LSize * SizeOf(Char)); + // ':' + while (p^ <> #0) and ((p^ = ':') or (p^ = ' ')) do + Inc(p); + + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> #13) do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LValue, LSize); + Move(q^, Pointer(LValue)^, LSize * SizeOf(Char)); + // #13#10 + while (p^ <> #0) and ((p^ = #13) or (p^ = #10)) do + Inc(p); + + Add(LName, LValue); + end; +end; + +function THttpHeader.Encode: string; +var + I: Integer; +begin + Result := ''; + for I := 0 to FParams.Count - 1 do + begin + Result := Result + FParams[I].Name; + Result := Result + ': ' + FParams[I].Value + #13#10; + end; + Result := Result + #13#10; +end; + +{ TDelimitParams } + +procedure TDelimitParams.Decode(const AEncodedParams: string; AClear: Boolean); +var + p, q: PChar; + LName, LValue: string; + LSize: Integer; +begin + if AClear then + FParams.Clear; + + p := PChar(AEncodedParams); + while (p^ <> #0) do + begin + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> '=') do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LName, LSize); + Move(q^, Pointer(LName)^, LSize * SizeOf(Char)); + // '=' + while (p^ <> #0) and (p^ = '=') do + Inc(p); + + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> FDelimiter) do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LValue, LSize); + Move(q^, Pointer(LValue)^, LSize * SizeOf(Char)); + LValue := TNetEncoding.URL.Decode(LValue); + // ';' + while (p^ <> #0) and ((p^ = FDelimiter) or (p^ = ' ')) do + Inc(p); + + Add(LName, LValue); + end; +end; + +function TDelimitParams.Encode: string; +var + I: Integer; +begin + Result := ''; + for I := 0 to FParams.Count - 1 do + begin + if (I > 0) then + Result := Result + FDelimiter + ' '; + Result := Result + FParams[I].Name + '=' + TNetEncoding.URL.Encode(FParams[I].Value); + end; +end; + +{ TRequestCookies } + +procedure TRequestCookies.Decode(const AEncodedParams: string; AClear: Boolean); +var + p, q: PChar; + LName, LValue: string; + LSize: Integer; +begin + if AClear then + FParams.Clear; + + p := PChar(AEncodedParams); + while (p^ <> #0) do + begin + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> '=') do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LName, LSize); + Move(q^, Pointer(LName)^, LSize * SizeOf(Char)); + // '=' + while (p^ <> #0) and (p^ = '=') do + Inc(p); + + q := p; + LSize := 0; + while (p^ <> #0) and (p^ <> ';') do + begin + Inc(LSize); + Inc(p); + end; + SetLength(LValue, LSize); + Move(q^, Pointer(LValue)^, LSize * SizeOf(Char)); + LValue := TNetEncoding.URL.Decode(LValue); + // ';' + while (p^ <> #0) and ((p^ = ';') or (p^ = ' ')) do + Inc(p); + + Add(LName, LValue); + end; +end; + +function TRequestCookies.Encode: string; +var + I: Integer; +begin + Result := ''; + for I := 0 to FParams.Count - 1 do + begin + if (I > 0) then + Result := Result + '; '; + Result := Result + FParams[I].Name + '=' + TNetEncoding.URL.Encode(FParams[I].Value); + end; +end; + +{ TResponseCookie } + +constructor TResponseCookie.Create(const AName, AValue: string; + AMaxAge: Integer; const APath, ADomain: string; AHttpOnly, ASecure: Boolean); +begin + Name := AName; + Value := AValue; + MaxAge := AMaxAge; + Path := APath; + Domain := ADomain; + HttpOnly := AHttpOnly; + Secure := ASecure; +end; + +function TResponseCookie.Encode: string; +begin + Result := Name + '=' + TNetEncoding.URL.Encode(Value); + + if (MaxAge > 0) then + begin + Result := Result + '; Max-Age=' + MaxAge.ToString; + Result := Result + '; Expires=' + TCrossHttpUtils.RFC1123_DateToStr(Now.AddSeconds(MaxAge)); + end; + if (Path <> '') then + Result := Result + '; Path=' + Path; + if (Domain <> '') then + Result := Result + '; Domain=' + Domain; + if HttpOnly then + Result := Result + '; HttpOnly'; + if Secure then + Result := Result + '; Secure'; +end; + +{ TFormField } + +constructor TFormField.Create; +begin +end; + +destructor TFormField.Destroy; +begin + FreeValue; + + inherited; +end; + +procedure TFormField.FreeValue; +begin + if Assigned(FValue) then + FreeAndNil(FValue); +end; + +function TFormField.AsBytes: TBytes; +var + LBytesStream: TBytesStream; +begin + if (FValue = nil) or (FValue.Size <= 0) then Exit(nil); + + if (FValue is TBytesStream) then + begin + Result := TBytesStream(FValue).Bytes; + SetLength(Result, FValue.Size); + end else + begin + LBytesStream := TBytesStream.Create; + try + LBytesStream.CopyFrom(FValue, 0); + Result := LBytesStream.Bytes; + SetLength(Result, LBytesStream.Size); + finally + FreeAndNil(LBytesStream); + end; + end; +end; + +function TFormField.AsString(AEncoding: TEncoding): string; +begin + if (AEncoding = nil) then + AEncoding := TEncoding.UTF8; + + Result := AEncoding.GetString(AsBytes); +end; + +{ THttpMultiPartFormData.TEnumerator } + +constructor THttpMultiPartFormData.TEnumerator.Create( + const AList: TList); +begin + inherited Create; + FList := AList; + FIndex := -1; +end; + +function THttpMultiPartFormData.TEnumerator.DoGetCurrent: TFormField; +begin + Result := FList[FIndex]; +end; + +function THttpMultiPartFormData.TEnumerator.DoMoveNext: Boolean; +begin + if (FIndex >= FList.Count) then + Exit(False); + Inc(FIndex); + Result := (FIndex < FList.Count); +end; + +{ THttpMultiPartFormData } + +constructor THttpMultiPartFormData.Create; +begin + FDecodeState := dsBoundary; + FCurrentPartHeader := TBytesStream.Create(nil); + FPartFields := TObjectList.Create(True); +end; + +destructor THttpMultiPartFormData.Destroy; +begin + Clear; + FreeAndNil(FCurrentPartHeader); + FreeAndNil(FPartFields); + inherited; +end; + +procedure THttpMultiPartFormData.Clear; +var + LField: TFormField; +begin + for LField in FPartFields do + begin + if FAutoDeleteFiles and TFile.Exists(LField.FilePath) then + begin + LField.FreeValue; + TFile.Delete(LField.FilePath); + end; + end; + + FPartFields.Clear; +end; + +function THttpMultiPartFormData.DoGetEnumerator: TEnumerator; +begin + Result := TEnumerator.Create(FPartFields); +end; + +function THttpMultiPartFormData.GetItem(AIndex: Integer): TFormField; +begin + Result := FPartFields.Items[AIndex]; +end; + +function THttpMultiPartFormData.GetItemIndex(const AName: string): Integer; +var + I: Integer; +begin + for I := 0 to FPartFields.Count - 1 do + if SameText(FPartFields[I].Name, AName) then Exit(I); + Result := -1; +end; + +function THttpMultiPartFormData.GetCount: Integer; +begin + Result := FPartFields.Count; +end; + +function THttpMultiPartFormData.GetDataSize: Integer; +var + LPartField: TFormField; +begin + Result := 0; + for LPartField in FPartFields do + Inc(Result, LPartField.FValue.Size); +end; + +function THttpMultiPartFormData.GetField(const AName: string): TFormField; +var + I: Integer; +begin + I := GetItemIndex(AName); + if (I >= 0) then + Exit(FPartFields[I]); + Result := nil; +end; + +procedure THttpMultiPartFormData.InitWithBoundary(const ABoundary: string); +begin + Clear; + FBoundary := ABoundary; + FBoundaryBytes := TEncoding.ANSI.GetBytes(#13#10'--' + FBoundary); + FDecodeState := dsBoundary; + FBoundaryIndex := 0; + FCurrentPartHeader.Clear; + SetLength(FLookbehind, Length(FBoundaryBytes) + 8); +end; + +function THttpMultiPartFormData.Decode(const ABuf: Pointer; ALen: Integer): Integer; + function __NewFileID: string; + begin + Result := TUtils.GetGUID.ToLower; + end; + + procedure __InitFormFieldByHeader(AFormField: TFormField; const AHeader: string); + var + LFieldHeader: THttpHeader; + LContentDisposition: string; + LMatch: TMatch; + begin + LFieldHeader := THttpHeader.Create; + try + LFieldHeader.Decode(AHeader); + LContentDisposition := LFieldHeader['Content-Disposition']; + if (LContentDisposition = '') then Exit; + + LMatch := TRegEx.Match(LContentDisposition, '\bname="(.*?)"(?=;|$)', [TRegExOption.roIgnoreCase]); + if LMatch.Success then + AFormField.FName := LMatch.Groups[1].Value; + + LMatch := TRegEx.Match(LContentDisposition, '\bfilename="(.*?)"(?=;|$)', [TRegExOption.roIgnoreCase]); + if LMatch.Success then + begin + AFormField.FFileName := LMatch.Groups[1].Value; + AFormField.FFilePath := TPath.Combine(FStoragePath, + __NewFileID + TPath.GetExtension(AFormField.FFileName)); + if TFile.Exists(AFormField.FFilePath) then + TFile.Delete(AFormField.FFilePath); + AFormField.FValue := TFile.Open(AFormField.FFilePath, TFileMode.fmOpenOrCreate, TFileAccess.faReadWrite, TFileShare.fsRead); + end else + AFormField.FValue := TBytesStream.Create(nil); + + AFormField.FContentType := LFieldHeader['Content-Type']; + AFormField.FContentTransferEncoding := LFieldHeader['Content-Transfer-Encoding']; + finally + FreeAndNil(LFieldHeader); + end; + end; +var + C: Byte; + I: Integer; + P: PByteArray; + LPartHeader: string; +begin + if (FBoundaryBytes = nil) then Exit(0); + + P := ABuf; + I := 0; + while (I < ALen) do + begin + C := P[I]; + case FDecodeState of + // Boundary, ȷһ + dsBoundary: + begin + if (C = FBoundaryBytes[2 + FBoundaryIndex]) then + Inc(FBoundaryIndex) + else + FBoundaryIndex := 0; + // --Boundary + if (2 + FBoundaryIndex >= Length(FBoundaryBytes)) then + begin + FDecodeState := dsDetect; + CR := 0; + LF := 0; + FBoundaryIndex := 0; + FDetectHeaderIndex := 0; + FDetectEndIndex := 0; + end; + end; + + // ͨBoundary, ȷݻѵ + dsDetect: + begin + if (C = DETECT_HEADER_BYTES[FDetectHeaderIndex]) then + Inc(FDetectHeaderIndex) + else + FDetectHeaderIndex := 0; + + if (C = DETECT_END_BYTES[FDetectEndIndex]) then + Inc(FDetectEndIndex) + else + FDetectEndIndex := 0; + + // Ƿ + if (FDetectHeaderIndex = 0) and (FDetectEndIndex = 0) then Exit(I); + + // ⵽־ + // --Boundary--#13#10 + if (FDetectEndIndex >= Length(DETECT_END_BYTES)) then + begin + FDecodeState := dsBoundary; + CR := 0; + LF := 0; + FBoundaryIndex := 0; + FDetectEndIndex := 0; + end else + // 滹 + // --Boundary#13#10 + if (FDetectHeaderIndex >= Length(DETECT_HEADER_BYTES)) then + begin + FCurrentPartHeader.Clear; + FDecodeState := dsPartHeader; + CR := 0; + LF := 0; + FBoundaryIndex := 0; + FDetectHeaderIndex := 0; + end; + end; + + dsPartHeader: + begin + case C of + 13: Inc(CR); + 10: Inc(LF); + else + CR := 0; + LF := 0; + end; + + // ͷݵ, , ͻ˹, һ + // ޱȾ޴ͷ, ͻɻռùڴ, пڴ + // һͷߴ(MAX_PART_HEADER) + // ***ԽһŻ***: + // Բʹʱ, ֱӴABufнͷ, ͷݱ + // ABufʱȽ鷳 + FCurrentPartHeader.Write(C, 1); + // ͷ, ΪǷ + if (FCurrentPartHeader.Size > MAX_PART_HEADER) then Exit(I); + + // ͷ + // #13#10#13#10 + if (CR = 2) and (LF = 2) then + begin + // ͷͨUTF8 + LPartHeader := TEncoding.UTF8.GetString(FCurrentPartHeader.Bytes, 0, FCurrentPartHeader.Size - 4{#13#10#13#10}); + FCurrentPartHeader.Clear; + FCurrentPartField := TFormField.Create; + __InitFormFieldByHeader(FCurrentPartField, LPartHeader); + FPartFields.Add(FCurrentPartField); + + FDecodeState := dsPartData; + CR := 0; + LF := 0; + FPartDataBegin := -1; + FBoundaryIndex := 0; + FPrevIndex := 0; + end; + end; + + dsPartData: + begin + // һµݿ, Ҫݿʼλ + if (FPartDataBegin < 0) and (FPrevIndex = 0) then + FPartDataBegin := I; + + // Boundary + if (C = FBoundaryBytes[FBoundaryIndex]) then + Inc(FBoundaryIndex) + else + begin + if (FBoundaryIndex > 0) then + begin + Dec(I); + FBoundaryIndex := 0; + end; + + if (FPartDataBegin < 0) then + FPartDataBegin := I; + end; + + // һڴβвеBoundary, һж + if (FPrevIndex > 0) then + begin + // ǰֽȻܸBoundaryƥ, 䱣һ + if (FBoundaryIndex > 0) then + begin + FLookbehind[FPrevIndex] := C; + Inc(FPrevIndex); + end else + // ǰֽBoundaryƥ, ô˵֮ǰеBoundary + // Boundary, ݿе, Field + begin + FCurrentPartField.FValue.Write(FLookbehind[0], FPrevIndex); + FPrevIndex := 0; + end; + end; + + // ѵڴѾһݿ + if (I >= ALen - 1) or (FBoundaryIndex >= Length(FBoundaryBytes)) then + begin + // ڴݴField + if (FPartDataBegin >= 0) then + FCurrentPartField.FValue.Write(P[FPartDataBegin], I - FPartDataBegin - FBoundaryIndex + 1); + + // ѽһݿ + if (FBoundaryIndex >= Length(FBoundaryBytes)) then + begin + FCurrentPartField.FValue.Position := 0; + FDecodeState := dsDetect; + FBoundaryIndex := 0; + end else + // ѽڴβ, Ƿ˲еBoundary + // 䱣 + if (FPrevIndex = 0) and (FBoundaryIndex > 0) then + begin + FPrevIndex := FBoundaryIndex; + Move(P[I - FBoundaryIndex + 1], FLookbehind[0], FBoundaryIndex); + end; + + // ݿʼλҪ֮ + FPartDataBegin := -1; + end; + end; + end; + + Inc(I); + end; + + Result := ALen; +end; + +{ TResponseCookies } + +procedure TResponseCookies.AddOrSet(const AName, AValue: string; + AMaxAge: Integer; const APath, ADomain: string; AHttpOnly, ASecure: Boolean); +begin + SetCookie(AName, TResponseCookie.Create(AName, AValue, AMaxAge, APath, ADomain, AHttpOnly, ASecure)); +end; + +function TResponseCookies.GetCookieIndex(const AName: string): Integer; +var + I: Integer; +begin + for I := 0 to Count - 1 do + if SameText(Items[I].Name, AName) then Exit(I); + Result := -1; +end; + +procedure TResponseCookies.Remove(const AName: string); +var + I: Integer; +begin + I := GetCookieIndex(AName); + if (I >= 0) then + inherited Delete(I); +end; + +function TResponseCookies.GetCookie(const AName: string): TResponseCookie; +var + I: Integer; +begin + I := GetCookieIndex(AName); + if (I >= 0) then + Result := Items[I] + else + begin + Result := TResponseCookie.Create(AName, '', 0); + Add(Result); + end; +end; + +procedure TResponseCookies.SetCookie(const AName: string; + const Value: TResponseCookie); +var + I: Integer; +begin + I := GetCookieIndex(AName); + if (I >= 0) then + Items[I] := Value + else + Add(Value); +end; + +{ TSessionBase } + +constructor TSessionBase.Create(const ASessionID: string); +begin + SetSessionID(ASessionID); + SetCreateTime(Now); + SetLastAccessTime(Now); +end; + +function TSessionBase.Expired: Boolean; +begin + Result := (Now.SecondsDiffer(LastAccessTime) >= ExpiryTime); +end; + +procedure TSessionBase.Touch; +begin + LastAccessTime := Now; +end; + +{ TSession } + +constructor TSession.Create(const ASessionID: string); +begin + FValues := TDictionary.Create; + + inherited; +end; + +destructor TSession.Destroy; +begin + FreeAndNil(FValues); + inherited; +end; + +function TSession.GetCreateTime: TDateTime; +begin + Result := FCreateTime; +end; + +function TSession.GetExpiryTime: Integer; +begin + Result := FExpire; +end; + +function TSession.GetLastAccessTime: TDateTime; +begin + Result := FLastAccessTime; +end; + +function TSession.GetSessionID: string; +begin + Result := FSessionID; +end; + +function TSession.GetValue(const AName: string): string; +begin + if not FValues.TryGetValue(AName, Result) then + Result := ''; + FLastAccessTime := Now; +end; + +procedure TSession.SetCreateTime(const ACreateTime: TDateTime); +begin + FCreateTime := ACreateTime; +end; + +procedure TSession.SetExpiryTime(const AValue: Integer); +begin + FExpire := AValue; +end; + +procedure TSession.SetLastAccessTime(const ALastAccessTime: TDateTime); +begin + FLastAccessTime := ALastAccessTime; +end; + +procedure TSession.SetSessionID(const ASessionID: string); +begin + FSessionID := ASessionID; +end; + +procedure TSession.SetValue(const AName, AValue: string); +begin + if (AValue <> '') then + FValues.AddOrSetValue(AName, AValue) + else + FValues.Remove(AName); + FLastAccessTime := Now; +end; + +{ TSessionsBase } + +function TSessionsBase.AddSession(const ASessionID: string): ISession; +begin + Result := GetSessionClass.Create(ASessionID); + Result.ExpiryTime := ExpiryTime; + AddSession(ASessionID, Result); +end; + +function TSessionsBase.AddSession: ISession; +begin + Result := AddSession(NewSessionID); +end; + +function TSessionsBase.ExistsSession(const ASessionID: string): Boolean; +var + LStuff: ISession; +begin + Result := ExistsSession(ASessionID, LStuff); +end; + +{ TSessions } + +constructor TSessions.Create(ANewGUIDFunc: TFunc); +begin + FNewGUIDFunc := ANewGUIDFunc; + FSessions := TDictionary.Create; + FLocker := TMultiReadExclusiveWriteSynchronizer.Create; + FSessionClass := TSession; + CreateExpiredProcThread; +end; + +constructor TSessions.Create; +begin + Create(nil); +end; + +destructor TSessions.Destroy; +begin + FShutdown := True; + while FExpiredProcRunning do Sleep(10); + + BeginWrite; + FSessions.Clear; + EndWrite; + FreeAndNil(FLocker); + FreeAndNil(FSessions); + + inherited; +end; + +procedure TSessions.AddSession(const ASessionID: string; ASession: ISession); +begin + if (ASession.ExpiryTime <= 0) then + ASession.ExpiryTime := ExpiryTime; + FSessions.AddOrSetValue(ASessionID, ASession); +end; + +procedure TSessions.BeginRead; +begin + FLocker.BeginRead; +end; + +procedure TSessions.BeginWrite; +begin + FLocker.BeginWrite; +end; + +procedure TSessions.EndRead; +begin + FLocker.EndRead; +end; + +procedure TSessions.EndWrite; +begin + FLocker.EndWrite; +end; + +function TSessions.ExistsSession(const ASessionID: string; + var ASession: ISession): Boolean; +begin + Result := FSessions.TryGetValue(ASessionID, ASession); + if Result then + ASession.LastAccessTime := Now; +end; + +procedure TSessions.CreateExpiredProcThread; +begin + TThread.CreateAnonymousThread( + procedure + procedure _ClearExpiredSessions; + var + LPair: TPair; + begin + BeginWrite; + try + for LPair in FSessions do + begin + if FShutdown then Break; + + if LPair.Value.Expired then + RemoveSession(LPair.Key); + end; + finally + EndWrite; + end; + end; + var + LWatch: TStopwatch; + begin + FExpiredProcRunning := True; + try + LWatch := TStopwatch.StartNew; + while not FShutdown do + begin + // ÿ 5 һγʱ Session + if (FExpire > 0) and (LWatch.Elapsed.TotalMinutes >= 1) then + begin + _ClearExpiredSessions; + LWatch.Reset; + LWatch.Start; + end; + Sleep(10); + end; + finally + FExpiredProcRunning := False; + end; + end).Start; +end; + +function TSessions.NewSessionID: string; +begin + if Assigned(FNewGUIDFunc) then + Result := FNewGUIDFunc() + else + Result := TUtils.GetGUID.ToLower; +end; + +function TSessions.GetCount: Integer; +begin + Result := FSessions.Count; +end; + +function TSessions.GetEnumerator: TEnumerator; +begin + Result := TDictionary.TValueEnumerator.Create(FSessions); +end; + +function TSessions.GetExpiryTime: Integer; +begin + Result := FExpire; +end; + +function TSessions.GetItem(const AIndex: Integer): ISession; +var + LIndex: Integer; + LPair: TPair; +begin + LIndex := 0; + for LPair in FSessions do + begin + if (LIndex = AIndex) then Exit(LPair.Value); + Inc(LIndex); + end; + Result := nil; +end; + +function TSessions.GetSession(const ASessionID: string): ISession; +var + LSessionID: string; +begin + LSessionID := ASessionID; + BeginWrite; + try + if (LSessionID = '') then + LSessionID := NewSessionID; + if not FSessions.TryGetValue(LSessionID, Result) then + begin + Result := FSessionClass.Create(LSessionID); + Result.ExpiryTime := ExpiryTime; + AddSession(LSessionID, Result); + end; + finally + EndWrite; + end; + + Result.LastAccessTime := Now; +end; + +function TSessions.GetSessionClass: TSessionClass; +begin + Result := FSessionClass; +end; + +procedure TSessions.RemoveSession(const ASessionID: string); +begin + FSessions.Remove(ASessionID); +end; + +procedure TSessions.SetExpiryTime(const Value: Integer); +begin + FExpire := Value; +end; + +procedure TSessions.SetSessionClass(const Value: TSessionClass); +begin + FSessionClass := Value; +end; + +end. + diff --git a/ThirdParty/DCS/Net/Net.CrossHttpRouter.pas b/ThirdParty/DCS/Net/Net.CrossHttpRouter.pas new file mode 100644 index 00000000..7b7a9ac2 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossHttpRouter.pas @@ -0,0 +1,418 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossHttpRouter; + +interface + +uses + Net.CrossHttpServer; + +type + /// + /// · + /// + /// + /// TCrossHttpServer.Route(), Get(), Post() + /// + TNetCrossRouter = class + public + /// + /// ̬ļ· + /// + /// + /// Ŀ¼ + /// + class function &Static(const ALocalDir, AFileParamName: string): TCrossHttpRouterProc2; static; + + /// + /// ļб· + /// + /// + /// ·, òΪĿ¼бҳжλ· + /// + /// + /// Ŀ¼ + /// + class function Dir(const APath, ALocalDir, ADirParamName: string): TCrossHttpRouterProc2; static; + + /// + /// Ĭҳļľ̬ļ· + /// + /// + /// ĬҳļıĿ¼ + /// + /// + /// Ĭϵҳļ,˳ѡ,ҵĸʹĸ + /// + class function Index(const ALocalDir, AFileParamName: string; const ADefIndexFiles: TArray): TCrossHttpRouterProc2; static; + end; + +implementation + +uses + System.SysUtils, System.Classes, System.IOUtils, System.NetEncoding; + +{ TNetCrossRouter } + +class function TNetCrossRouter.Index(const ALocalDir, AFileParamName: string; + const ADefIndexFiles: TArray): TCrossHttpRouterProc2; +var + LDefIndexFiles: TArray; +begin + if (ADefIndexFiles <> nil) then + LDefIndexFiles := ADefIndexFiles + else + LDefIndexFiles := [ + 'index.html', + 'main.html', + 'index.js', + 'main.js', + 'index.htm', + 'main.htm' + ]; + + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + var + LPath, LFile, LDefMainFile: string; + begin + LPath := ALocalDir; + LFile := ARequest.Params[AFileParamName]; + + if (LFile = '') then + begin + for LDefMainFile in LDefIndexFiles do + begin + LFile := TPath.Combine(LPath, LDefMainFile); + if TFile.Exists(LFile) then + begin + AResponse.SendFile(LFile); + AHandled := True; + Exit; + end; + end; + end else + begin + LFile := TPath.Combine(LPath, LFile); + if TFile.Exists(LFile) then + begin + AResponse.SendFile(LFile); + AHandled := True; + Exit; + end; + end; + + AHandled := False; + end; +end; + +class function TNetCrossRouter.Static( + const ALocalDir, AFileParamName: string): TCrossHttpRouterProc2; +begin + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + var + LFile: string; + begin + AHandled := True; + LFile := TPath.Combine(ALocalDir, ARequest.Params[AFileParamName]); + if (LFile = '') then + begin + AHandled := False; + Exit; + end; + LFile := TPath.GetFullPath(LFile); + AResponse.SendFile(LFile); + end; +end; + +{$region 'Dir'} +type + THttpFileEntry = class + Name: string; + Size: Int64; + Time: TDateTime; + Directory: Boolean; + ReadOnly: Boolean; + SysFile: Boolean; + Hidden: Boolean; + end; + +function BuildDirList(const ARealPath, ARequestPath, AHome: string): string; + function SmartSizeToStr(ABytes: Int64): string; + const + KBYTES = Int64(1024); + MBYTES = KBYTES * 1024; + GBYTES = MBYTES * 1024; + TBYTES = GBYTES * 1024; + PBYTES = TBYTES * 1024; + begin + if (ABytes < KBYTES) then + Result := Format('%dB', [ABytes]) + else if (ABytes < MBYTES) then + Result := Format('%.2fK ', [ABytes / KBYTES]) + else if (ABytes < GBYTES) then + Result := Format('%.2fM ', [ABytes / MBYTES]) + else if (ABytes < TBYTES) then + Result := Format('%.2fG ', [ABytes / GBYTES]) + else if (ABytes < PBYTES) then + Result := Format('%.2fT ', [ABytes / TBYTES]) + else + Result := Format('%.2fP ', [ABytes / PBYTES]); + end; + + function FormatDirEntry(const APath: string; F: THttpFileEntry): string; + var + Attr, Link, NameString, SizeString: string; + begin + if (F.Name = '.') or (F.Name = '..') then + begin + Result := ''; + Exit; + end; + + // drwsh + Attr := '-rw--'; + if F.Directory then + begin + Attr[1] := 'd'; + SizeString := ''; + NameString := '' + F.Name + ''; + end + else + begin + SizeString := SmartSizeToStr(F.Size); + NameString := F.Name; + end; + + if F.ReadOnly then + Attr[3] := '-'; + + if F.SysFile then + Attr[4] := 's'; + + if F.Hidden then + Attr[5] := 'h'; + + if (APath[Length(APath)] = '/') then + Link := TNetEncoding.URL.Encode(F.Name) + else + Link := APath + '/' + TNetEncoding.URL.Encode(F.Name); + + Result := + '' + NameString + '' + + '' + Attr + '' + + '' + SizeString + '' + + '' + + '' + FormatDateTime('YYYY-MM-DD HH:NN:SS', F.Time) + ''; + end; + + function PathToURL(const APath, AHome: string): string; + function _NormalizePath(const APathStr: string): string; + begin + Result := APathStr.Replace('\/', '/').Replace('\\', '/').Replace('//', '/'); + if (Result = '') then + Result := '/' + else + begin + if (Result.Chars[0] <> '/') then + Result := '/' + Result; + if (Result.Chars[Result.Length - 1] <> '/') then + Result := Result + '/'; + end; + end; + var + LPath, LHome, LSubPath: string; + LPathArr, LHomeArr: TArray; + I: Integer; + begin + LPath := _NormalizePath(APath); + LHome := _NormalizePath(AHome); + + LPathArr := LPath.Split(['/', '\'], TStringSplitOptions.ExcludeEmpty); + LHomeArr := LHome.Split(['/', '\'], TStringSplitOptions.ExcludeEmpty); + if Length(LHomeArr) > Length(LPathArr) then Exit(''); + + I := 0; + while True do + begin + if (I >= Length(LPathArr)) or (I >= Length(LHomeArr)) + or not SameText(LPathArr[I], LHomeArr[I]) then Break; + Inc(I); + end; + + Result := Format('Home / ', + [LHome]); + LSubPath := LHome; + + while True do + begin + if (I >= Length(LPathArr)) then Break; + + LSubPath := LSubPath + LPathArr[I] + '/'; + Result := Result + Format('%s / ', + [LSubPath, LPathArr[I]]); + Inc(I); + end; + end; +var + Status: Integer; + F: TSearchRec; + DirList: TStringList; + FileList: TStringList; + Data: THttpFileEntry; + i: Integer; + Total: Cardinal; + TotalBytes: Int64; + HTML: string; +begin + DirList := TStringList.Create; + FileList := TStringList.Create; + Status := FindFirst(TPath.Combine(ARealPath, '*.*'), faAnyFile, F); + while Status = 0 do + begin + if (F.Name <> '.') and (F.Name <> '..') then + begin + Data := THttpFileEntry.Create; + Data.Name := F.Name; + Data.Size := F.Size; + Data.Time := F.TimeStamp; + Data.Directory := ((F.Attr and faDirectory) <> 0); + Data.ReadOnly := ((F.Attr and faReadOnly) <> 0); + Data.SysFile := ((F.Attr and faSysFile) <> 0); + Data.Hidden := ((F.Attr and faHidden) <> 0); + + if ((F.Attr and faDirectory) <> 0) then + DirList.AddObject(Data.Name, Data) + else + FileList.AddObject(Data.Name, Data); + end; + + Status := FindNext(F); + end; + FindClose(F); + DirList.Sort; + FileList.Sort; + + HTML := + '' + + '' + + '' + + '' + + 'ļб' + + '' + + '' + + '' + + '' + + '
' + PathToURL(ARequestPath, AHome) + '

'; + + TotalBytes := 0; + Total := DirList.Count + FileList.Count; + if Total <= 0 then + HTML := HTML + '

Ŀ¼
' + else + begin + HTML := HTML + + // + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
ļС޸ʱ
' + + + // һɫ + '' + + '' + + '
' + + + // ļб + ''; + + for i := 0 to DirList.Count - 1 do + begin + Data := THttpFileEntry(DirList.Objects[i]); + HTML := HTML + '' + FormatDirEntry(ARequestPath, Data) + ''; + FreeAndNil(Data); + end; + + for i := 0 to FileList.Count - 1 do + begin + Data := THttpFileEntry(FileList.Objects[i]); + HTML := HTML + '' + FormatDirEntry(ARequestPath, Data) + ''; + TotalBytes := TotalBytes + Data.Size; + FreeAndNil(Data); + end; + + HTML := HTML + '
' + + // һɫ + '' + + '' + + '
' + + + // ҳͳϢ + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + Format('Ŀ¼: %d, ļ: %d', [DirList.Count, FileList.Count]) + '' + SmartSizeToStr(TotalBytes) + '
'; + end; + + FreeAndNil(DirList); + FreeAndNil(FileList); + + HTML := HTML + ''; + Result := HTML; +end; +{$endregion} + +class function TNetCrossRouter.Dir( + const APath, ALocalDir, ADirParamName: string): TCrossHttpRouterProc2; +begin + Result := + procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) + var + LFile: string; + begin + AHandled := True; + + LFile := TPath.Combine(ALocalDir, ARequest.Params[ADirParamName]); + if (LFile = '') then + begin + AHandled := False; + Exit; + end; + + LFile := TPath.GetFullPath(LFile); + if (TDirectory.Exists(LFile)) then + AResponse.Send(BuildDirList(LFile, ARequest.Path, APath)) + else if TFile.Exists(LFile) then + AResponse.SendFile(LFile) + else + AHandled := False; + end; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossHttpServer.pas b/ThirdParty/DCS/Net/Net.CrossHttpServer.pas new file mode 100644 index 00000000..a065bd1f --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossHttpServer.pas @@ -0,0 +1,4792 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossHttpServer; + +{ + Linux下需要安装zlib1g-dev开发包 + sudo apt-get install zlib1g-dev +} + +interface + +uses + System.Classes, + System.SysUtils, + System.StrUtils, + System.Math, + System.IOUtils, + System.Generics.Collections, + System.RegularExpressions, + System.NetEncoding, + System.RegularExpressionsCore, + System.RegularExpressionsConsts, + System.ZLib, + System.Hash, + Net.SocketAPI, + Net.CrossSocket.Base, + Net.CrossSocket, + Net.CrossServer, + {$IFDEF __CROSS_SSL__} + Net.CrossSslSocket, + Net.CrossSslServer, + {$ENDIF} + Net.CrossHttpParams, + Net.CrossHttpUtils, + Utils.Logger; + +const + CROSS_HTTP_SERVER_NAME = 'CrossHttpServer/2.0'; + MIN_COMPRESS_SIZE = 512; + +type + ECrossHttpException = class(Exception) + private + FStatusCode: Integer; + public + constructor Create(const AMessage: string; AStatusCode: Integer = 400); reintroduce; virtual; + constructor CreateFmt(const AMessage: string; const AArgs: array of const; AStatusCode: Integer = 400); reintroduce; virtual; + + property StatusCode: Integer read FStatusCode write FStatusCode; + end; + + ICrossHttpServer = interface; + ICrossHttpRequest = interface; + ICrossHttpResponse = interface; + + /// + /// HTTP连接接口 + /// + ICrossHttpConnection = interface(ICrossConnection) + ['{72E9AC44-958C-4C6F-8769-02EA5EC3E9A8}'] + function GetRequest: ICrossHttpRequest; + function GetResponse: ICrossHttpResponse; + function GetServer: ICrossHttpServer; + + /// + /// 请求对象 + /// + property Request: ICrossHttpRequest read GetRequest; + + /// + /// 响应对象 + /// + property Response: ICrossHttpResponse read GetResponse; + + /// + /// Server对象 + /// + property Server: ICrossHttpServer read GetServer; + end; + + /// + /// 请求体类型 + /// + TBodyType = (btNone, btUrlEncoded, btMultiPart, btBinary); + + /// + /// HTTP请求接口 + /// + ICrossHttpRequest = interface + ['{B26B7E7B-6B24-4D86-AB58-EBC20722CFDD}'] + function GetConnection: ICrossHttpConnection; + function GetRawRequestText: string; + function GetRawPathAndParams: string; + function GetMethod: string; + function GetPath: string; + function GetVersion: string; + function GetHeader: THttpHeader; + function GetCookies: TRequestCookies; + function GetSession: ISession; + function GetParams: THttpUrlParams; + function GetQuery: THttpUrlParams; + function GetBody: TObject; + function GetBodyType: TBodyType; + function GetKeepAlive: Boolean; + function GetAccept: string; + function GetAcceptEncoding: string; + function GetAcceptLanguage: string; + function GetReferer: string; + function GetUserAgent: string; + function GetIfModifiedSince: TDateTime; + function GetIfNoneMatch: string; + function GetRange: string; + function GetIfRange: string; + function GetAuthorization: string; + function GetXForwardedFor: string; + function GetContentLength: Int64; + function GetHostName: string; + function GetHostPort: Word; + function GetContentType: string; + function GetContentEncoding: string; + function GetRequestBoundary: string; + function GetRequestCmdLine: string; + function GetRequestConnection: string; + function GetTransferEncoding: string; + function GetIsChunked: Boolean; + function GetIsMultiPartFormData: Boolean; + function GetIsUrlEncodedFormData: Boolean; + function GetPostDataSize: Int64; + + /// + /// HTTP连接对象 + /// + property Connection: ICrossHttpConnection read GetConnection; + + /// + /// 原始请求数据 + /// + property RawRequestText: string read GetRawRequestText; + + /// + /// 原始请求路径及参数 + /// + property RawPathAndParams: string read GetRawPathAndParams; + + /// + /// 请求方法 + /// + /// + /// GET + /// + /// + /// POST + /// + /// + /// PUT + /// + /// + /// DELETE + /// + /// + /// HEAD + /// + /// + /// OPTIONS + /// + /// + /// TRACE + /// + /// + /// CONNECT
+ ///
+ /// + /// PATCH
+ ///
+ /// + /// COPY
+ ///
+ /// + /// LINK
+ ///
+ /// + /// UNLINK
+ ///
+ /// + /// PURGE
+ ///
+ /// + /// LOCK
+ ///
+ /// + /// UNLOCK
+ ///
+ /// + /// PROPFIND + /// + ///
+ ///
+ property Method: string read GetMethod; + + /// + /// + /// 请求路径, 不包含参数部分 + /// + /// + /// 比如: /api/callapi1 + /// + /// + property Path: string read GetPath; + + /// + /// 请求版本: + /// + /// + /// HTTP/1.0 + /// + /// + /// HTTP/1.1 + /// + /// + /// + property Version: string read GetVersion; + + /// + /// HTTP请求头 + /// + property Header: THttpHeader read GetHeader; + + /// + /// 客户端传递过来的Cookies + /// + property Cookies: TRequestCookies read GetCookies; + + /// + /// Session对象 + /// + /// + /// + /// 只有在Server开启了Session支持的情况, 该属性才有效, 否则该属性为nil + /// + /// + /// 要开启Server的Session支持, 只需要设置Server.SessionIDCookieName不为空即可 + /// + /// + property Session: ISession read GetSession; + + /// + /// + /// 请求路径中定义的参数 + /// + /// + /// 比如定义了一个Get('/echo/:text', cb) 然后有一个请求为 /echo/hello, 那么 Params + /// 中就会有一个名为 'text', 值为 'hello' 的参数 + /// + /// + property Params: THttpUrlParams read GetParams; + + /// + /// 请求路径后形如?key1=value1&key2=value2的参数 + /// + property Query: THttpUrlParams read GetQuery; + + /// + /// Body数据, 通过检查BodyType可以知道数据类型: + /// + /// + /// btNone(nil) + /// + /// + /// btUrlEncoded(THttpUrlParams) + /// + /// + /// btMultiPart(THttpMultiPartFormData) + /// + /// + /// btBinary(TBytesStream) + /// + /// + /// + property Body: TObject read GetBody; + + /// + /// Body的类型, + /// + /// + /// btNone(nil) + /// + /// + /// btUrlEncoded(THttpUrlParams) + /// + /// + /// btMultiPart(THttpMultiPartFormData) + /// + /// + /// btBinary(TBytesStream) + /// + /// + /// + property BodyType: TBodyType read GetBodyType; + + /// + /// KeepAliv标志 + /// + property KeepAlive: Boolean read GetKeepAlive; + + /// + /// 客户端能接收的数据种类 + /// + /// + /// image/webp,image/*,*/*;q=0.8 + /// + property Accept: string read GetAccept; + + /// + /// 客户端能接收的编码 + /// + /// + /// gzip, deflate, sdch + /// + property AcceptEncoding: string read GetAcceptEncoding; + + /// + /// 客户端能接收的语言 + /// + /// + /// zh-CN,zh;q=0.8 + /// + property AcceptLanguage: string read GetAcceptLanguage; + + /// + /// 参考地址, 描述该请求由哪个页面发出 + /// + property Referer: string read GetReferer; + + /// + /// 用户代理 + /// + /// + /// Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like + /// Gecko) Chrome/50.0.2661.102 Safari/537.36 + /// + property UserAgent: string read GetUserAgent; + + /// + /// 请求内容在浏览器端的缓存时间 + /// + property IfModifiedSince: TDateTime read GetIfModifiedSince; + + /// + /// 请求内容在浏览器端的标记 + /// + property IfNoneMatch: string read GetIfNoneMatch; + + /// + /// 请求分块传输 + /// + property Range: string read GetRange; + + /// + /// 请求分块传输时传往服务器的标记, 用于服务器比较数据是否已发生变化 + /// + property IfRange: string read GetIfRange; + + /// + /// 简单认证信息 + /// + property Authorization: string read GetAuthorization; + + /// + /// 通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段 + /// + property XForwardedFor: string read GetXForwardedFor; + + /// + /// 请求数据长度 + /// + property ContentLength: Int64 read GetContentLength; + + /// + /// 请求的主机名(域名、IP) + /// + property HostName: string read GetHostName; + + /// + /// 请求的主机端口 + /// + property HostPort: Word read GetHostPort; + + /// + /// 内容类型 + /// + property ContentType: string read GetContentType; + + /// + /// 请求命令行(也就是HTTP请求的第一行) + /// + property RequestCmdLine: string read GetRequestCmdLine; + + /// + /// 请求分界符 + /// + property RequestBoundary: string read GetRequestBoundary; + + /// + /// 传输编码 + /// + property TransferEncoding: string read GetTransferEncoding; + + /// + /// 内容编码 + /// + property ContentEncoding: string read GetContentEncoding; + + /// + /// 连接方式 + /// + property RequestConnection: string read GetRequestConnection; + + /// + /// 请求数据是否使用块编码 + /// + property IsChunked: Boolean read GetIsChunked; + + /// + /// 请求数据是使用 multipart/form-data 方式提交的 + /// + property IsMultiPartFormData: Boolean read GetIsMultiPartFormData; + + /// + /// 请求数据是使用 application/x-www-form-urlencoded 方式提交的 + /// + property IsUrlEncodedFormData: Boolean read GetIsUrlEncodedFormData; + + /// + /// 请求数据大小 + /// + property PostDataSize: Int64 read GetPostDataSize; + end; + + /// + /// 压缩类型 + /// + TCompressType = (ctGZip, ctDeflate); + + /// + /// HTTP应答接口 + /// + ICrossHttpResponse = interface + ['{5E15C20F-E221-4B10-90FC-222173A6F3E8}'] + function GetConnection: ICrossHttpConnection; + function GetRequest: ICrossHttpRequest; + function GetStatusCode: Integer; + procedure SetStatusCode(Value: Integer); + function GetContentType: string; + procedure SetContentType(const Value: string); + function GetLocation: string; + procedure SetLocation(const Value: string); + function GetHeader: THttpHeader; + function GetCookies: TResponseCookies; + function GetSent: Boolean; + + /// + /// 压缩发送块数据 + /// + /// + /// 产生块数据的匿名函数 + /// // AData: 数据指针 + /// // ACount: 数据大小 + /// // Result: 如果返回True, 则发送数据; 如果返回False, 则忽略AData和ACount并结束发送 + /// function(AData: PPointer; ACount: PNativeInt): Boolean + /// begin + /// end + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + /// + /// 本方法实现了一边压缩一边发送数据, 所以可以支持无限大的分块数据的压缩发送, 而不用占用太多的内存和CPU

+ /// zlib参考手册:
+ ///
+ procedure SendZCompress(const AChunkSource: TFunc; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 压缩发送无类型数据 + /// + /// + /// 无类型数据 + /// + /// + /// 数据大小 + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + procedure SendZCompress(const ABody; ACount: NativeInt; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 压缩发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + procedure SendZCompress(const ABody: TBytes; AOffset, ACount: NativeInt; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 压缩发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + procedure SendZCompress(const ABody: TBytes; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 压缩发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure SendZCompress(const ABody: TStream; const AOffset, ACount: Int64; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 压缩发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure SendZCompress(const ABody: TStream; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 压缩发送字符串数据 + /// + /// + /// 字符串数据 + /// + /// + /// 压缩方式 + /// + /// + /// 回调函数 + /// + procedure SendZCompress(const ABody: string; ACompressType: TCompressType; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送块数据 + /// + /// + /// 产生块数据的匿名函数 + /// // AData: 数据指针 + /// // ACount: 数据大小 + /// // Result: 如果返回True, 则发送数据; 如果返回False, 则忽略AData和ACount并结束发送 + /// function(AData: PPointer; ACount: PNativeInt): Boolean + /// begin + /// end + /// + /// + /// 回调函数 + /// + /// + /// 使用该方法可以一边生成数据一边发送, 无需等待数据全部准备完成 + /// + procedure SendNoCompress(const AChunkSource: TFunc; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送无类型数据 + /// + /// + /// 无类型数据 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + procedure SendNoCompress(const ABody; ACount: NativeInt; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + procedure SendNoCompress(const ABody: TBytes; AOffset, ACount: NativeInt; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 回调函数 + /// + procedure SendNoCompress(const ABody: TBytes; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure SendNoCompress(const ABody: TStream; const AOffset, ACount: Int64; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure SendNoCompress(const ABody: TStream; ACallback: TProc = nil); overload; + + /// + /// 不压缩发送字符串数据 + /// + /// + /// 字符串数据 + /// + /// + /// 回调函数 + /// + procedure SendNoCompress(const ABody: string; ACallback: TProc = nil); overload; + + /// + /// 发送无类型数据 + /// + /// + /// 无类型数据 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数
+ /// + procedure Send(const ABody; ACount: NativeInt; ACallback: TProc = nil); overload; + + /// + /// 发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数
+ /// + procedure Send(const ABody: TBytes; AOffset, ACount: NativeInt; ACallback: TProc = nil); overload; + + /// + /// 发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 回调函数
+ /// + procedure Send(const ABody: TBytes; ACallback: TProc = nil); overload; + + /// + /// 发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure Send(const ABody: TStream; const AOffset, ACount: Int64; ACallback: TProc = nil); overload; + + /// + /// 发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure Send(const ABody: TStream; ACallback: TProc = nil); overload; + + /// + /// 发送字符串数据 + /// + /// + /// 字符串数据 + /// + /// + /// 回调函数
+ /// + procedure Send(const ABody: string; ACallback: TProc = nil); overload; + + /// + /// 发送Json字符串数据 + /// + /// + /// Json字符串数据 + /// + /// + /// 回调函数
+ /// + procedure Json(const AJson: string; ACallback: TProc = nil); + + /// + /// 发送文件内容 + /// + /// + /// 文件名 + /// + /// + /// 回调函数 + /// + procedure SendFile(const AFileName: string; ACallback: TProc = nil); + + /// + /// 将文件以下载形式发送 + /// + /// + /// 文件名 + /// + /// + /// 回调函数 + /// + procedure Download(const AFileName: string; ACallback: TProc = nil); + + /// + /// 发送状态码 + /// + /// + /// 状态码 + /// + /// + /// 描述信息(body) + /// + /// + /// 回调函数 + /// + /// + /// 描述信息即是body数据, 如果设置为空, 则body也为空 + /// + procedure SendStatus(AStatusCode: Integer; const ADescription: string; + ACallback: TProc = nil); overload; + + /// + /// 发送状态码 + /// + /// + /// 状态码 + /// + /// + /// 回调函数 + /// + /// + /// 该方法根据状态码生成默认的body数据 + /// + procedure SendStatus(AStatusCode: Integer; + ACallback: TProc = nil); overload; + + /// + /// 发送重定向Url命令 + /// + /// + /// 新的Url + /// + /// + /// 回调函数 + /// + procedure Redirect(const AUrl: string; ACallback: TProc = nil); + + /// + /// 设置Content-Disposition, 令客户端将收到的数据作为文件下载处理 + /// + /// + /// 文件名 + /// + procedure Attachment(const AFileName: string); + + /// + /// HTTP连接对象 + /// + property Connection: ICrossHttpConnection read GetConnection; + + /// + /// 请求对象 + /// + property Request: ICrossHttpRequest read GetRequest; + + /// + /// 状态码 + /// + property StatusCode: Integer read GetStatusCode write SetStatusCode; + + /// + /// 内容类型 + /// + property ContentType: string read GetContentType write SetContentType; + + /// + /// 重定向Url + /// + property Location: string read GetLocation write SetLocation; + + /// + /// HTTP响应头 + /// + property Header: THttpHeader read GetHeader; + + /// + /// 设置Cookies + /// + property Cookies: TResponseCookies read GetCookies; + + /// + /// 是否已经发送数据 + /// + property Sent: Boolean read GetSent; + end; + + /// + /// 路由接口 + /// + ICrossHttpRouter = interface + ['{2B095450-6A5D-450F-8DCD-6911526C733F}'] + function GetMethod: string; + function GetPath: string; + function IsMatch(ARequest: ICrossHttpRequest): Boolean; + procedure Execute(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean); + + property Method: string read GetMethod; + property Path: string read GetPath; + end; + TCrossHttpRouters = TList; + + TCrossHttpRouterProc = reference to procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse); + TCrossHttpRouterMethod = procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse) of object; + TCrossHttpRouterProc2 = reference to procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean); + TCrossHttpRouterMethod2 = procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) of object; + + TCrossHttpConnEvent = procedure(Sender: TObject; AConnection: ICrossHttpConnection) of object; + TCrossHttpRequestEvent = procedure(Sender: TObject; ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean) of object; + TCrossHttpRequestExceptionEvent = procedure(Sender: TObject; ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; AException: Exception) of object; + TCrossHttpDataEvent = procedure(Sender: TObject; AClient: ICrossHttpConnection; ABuf: Pointer; ALen: Integer) of object; + + /// + /// + /// 跨平台HTTP服务器接口 + /// + /// + /// 路由定义方式: + /// + /// + /// Route(AMehod, APath, ARouter) + /// + /// + /// Get(APath, ARouter) + /// + /// + /// Put(APath, ARouter) + /// + /// + /// Post(APath, ARouter)
+ ///
+ /// + /// Delete(APath, ARouter)
+ ///
+ /// + /// All(APath, ARouter)
+ ///
+ /// + /// 其中AMehod和APath都支持正则表达式, ARouter可以是一个对象方法也可以是匿名函数
+ ///
+ ///
+ /// + /// + /// 这里偷了下懒, 没将HTTP和HTTPS分开实现两个不同的接口, 需要通过编译开关选择使用HTTP还是HTTP + /// + /// + /// 通过接口引用计数保证连接的有效性,所以可以在路由函数中调用线程池来处理业务逻辑,而不用担心处理过程中连接对象被释放 + /// + /// + /// 每个请求的响应流程大致为: + /// + /// + /// + /// 执行匹配的中间件; + /// + /// + /// 执行匹配的路由 + /// + /// + /// + /// + /// // 在线程池中处理业务逻辑 + /// FCrossHttpServer.Route('GET', '/runtask/:name', + /// procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse) + /// begin + /// System.Threading.TTask.Run( + /// procedure + /// begin + /// CallTask(ARequest.Params['name']); + /// end); + /// end); + /// // 正则表达式 + /// FCrossHttpServer.Route('GET', '/query/:count(\d+)', + /// procedure(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse) + /// begin + /// System.Threading.TTask.Run( + /// procedure + /// begin + /// CallQuery(ARequest.Params['count'].ToInteger); + /// end); + /// end); + /// + ICrossHttpServer = interface({$IFDEF __CROSS_SSL__}ICrossSslServer{$ELSE}ICrossServer{$ENDIF}) + ['{224D16AA-317C-435E-9C2E-92868E578DB3}'] + function GetStoragePath: string; + function GetAutoDeleteFiles: Boolean; + function GetMaxHeaderSize: Int64; + function GetMaxPostDataSize: Int64; + function GetCompressible: Boolean; + function GetMinCompressSize: Int64; + function GetSessions: ISessions; + function GetSessionIDCookieName: string; + function GetOnRequest: TCrossHttpRequestEvent; + function GetOnRequestException: TCrossHttpRequestExceptionEvent; + function GetOnPostDataBegin: TCrossHttpConnEvent; + function GetOnPostData: TCrossHttpDataEvent; + function GetOnPostDataEnd: TCrossHttpConnEvent; + + procedure SetStoragePath(const Value: string); + procedure SetAutoDeleteFiles(const Value: Boolean); + procedure SetMaxHeaderSize(const Value: Int64); + procedure SetMaxPostDataSize(const Value: Int64); + procedure SetCompressible(const Value: Boolean); + procedure SetMinCompressSize(const Value: Int64); + procedure SetSessions(const Value: ISessions); + procedure SetSessionIDCookieName(const Value: string); + procedure SetOnRequest(const Value: TCrossHttpRequestEvent); + procedure SetOnRequestException(const Value: TCrossHttpRequestExceptionEvent); + procedure SetOnPostDataBegin(const Value: TCrossHttpConnEvent); + procedure SetOnPostData(const Value: TCrossHttpDataEvent); + procedure SetOnPostDataEnd(const Value: TCrossHttpConnEvent); + + /// + /// 创建路由对象 + /// + /// + /// 请求方式 + /// + /// + /// 请求路径 + /// + /// + /// 路由匿名函数 + /// + /// + /// 路由方法 + /// + /// + /// 路由匿名函数 + /// + /// + /// 路由方法 + /// + function CreateRouter(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpRouter; + + /// + /// 注册中间件 + /// + /// + /// 请求方式 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名函数, 执行完处理函数之后还会继续执行后续匹配的中间件及路由 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const AMethod, APath: string; + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求方式 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名函数, 执行完处理函数之后, 如果AHandled=False则会继续执行后续匹配的中间件及路由, + /// 否则后续匹配的中间件及路由不会被执行 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const AMethod, APath: string; + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求方式 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名方法, 执行完处理方法之后还会继续执行后续匹配的中间件及路由 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const AMethod, APath: string; + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求方式 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名方法, 执行完处理方法之后, 如果AHandled=False则会继续执行后续匹配的中间件及路由, + /// 否则后续匹配的中间件及路由不会被执行 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const AMethod, APath: string; + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名函数, 执行完处理函数之后还会继续执行后续匹配的中间件及路由 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const APath: string; + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名函数, 执行完处理函数之后, 如果AHandled=False则会继续执行后续匹配的中间件及路由, + /// 否则后续匹配的中间件及路由不会被执行 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const APath: string; + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名方法, 执行完处理方法之后还会继续执行后续匹配的中间件及路由 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const APath: string; + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 请求路径 + /// + /// + /// 中间件处理匿名方法, 执行完处理方法之后, 如果AHandled=False则会继续执行后续匹配的中间件及路由, + /// 否则后续匹配的中间件及路由不会被执行 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(const APath: string; + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 中间件处理匿名函数, 执行完处理函数之后还会继续执行后续匹配的中间件及路由 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Use(AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册中间件 + /// + /// + /// 中间件处理方法, 执行完处理方法之后还会继续执行后续匹配的中间件及路由 + /// + /// + /// + /// + /// 中间件严格按照注册时的顺序被调用 + /// + /// + /// 中间件先于路由执行 + /// + /// + /// + function Use(AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Use(AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册路由(请求处理函数) + /// + /// + /// 请求方式, GET/POST/PUT/DELETE等, 支持正则表达式, * 表示处理全部请求方式 + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理匿名函数 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Route(const AMethod, APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Route(const AMethod, APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册路由(请求处理函数) + /// + /// + /// 请求方式, GET/POST/PUT/DELETE等, 支持正则表达式, * 表示处理全部请求方式 + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param + /// + /// + /// 路由处理方法 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Route(const AMethod, APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Route(const AMethod, APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册GET路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理匿名函数 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Get(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Get(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册GET路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理方法 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Get(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Get(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册PUT路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理匿名函数 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Put(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Put(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册PUT路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理方法 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Put(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Put(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册POST路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param + /// + /// + /// 路由处理匿名函数 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Post(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Post(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册POST路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理方法 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Post(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Post(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册DELETE路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param
+ /// + /// + /// 路由处理匿名函数 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Delete(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Delete(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册DELETE路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param + /// + /// + /// 路由处理方法 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function Delete(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Delete(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册全部请求方式路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param + /// + /// + /// 路由处理匿名函数 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function All(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function All(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + /// + /// 注册全部请求方式路由(请求处理函数) + /// + /// + /// 请求路径, 支持正则表达式, * 表示处理全部请求路径,
例如: + /// /path/:param1/:param2(\d+)|/path/:param + /// + /// + /// 路由处理方法 + /// + /// + /// + /// + /// 路由严格按照注册时的顺序被调用, 所以如果在注册了AMethod=*, + /// APath=*的路由之后,再注册的其它路由将不会被调用. 所以强烈建议把 "* 路由" 放到最后注册. + /// + /// + /// 路由中的正则表达式用法与node.js express相同 + /// + /// + /// + function All(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function All(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + /// + /// 注册静态文件路由 + /// + /// + /// 请求路径 + /// + /// + /// 静态文件目录, 该目录及子目录下的文件都将作为静态文件返回 + /// + function &Static(const APath, ALocalStaticDir: string): ICrossHttpServer; + + /// + /// 注册文件列表路由 + /// + /// + /// 请求路径 + /// + /// + /// 本地文件目录 + /// + function Dir(const APath, ALocalDir: string): ICrossHttpServer; + + /// + /// 注册含有默认首页文件的静态文件路由 + /// + /// + /// 请求路径 + /// + /// + /// 含有默认首页文件的本地目录 + /// + /// + /// 默认的首页文件,按顺序选择,先找到哪个就使用哪个 + /// + function Index(const APath, ALocalDir: string; const ADefIndexFiles: TArray): ICrossHttpServer; + + /// + /// 删除指定路由 + /// + function RemoveRouter(const AMethod, APath: string): ICrossHttpServer; + + /// + /// 清除所有路由 + /// + function ClearRouter: ICrossHttpServer; + + /// + /// 锁定并返回路由列表 + /// + function LockRouters: TCrossHttpRouters; + + /// + /// 解锁路由列表 + /// + procedure UnlockRouters; + + /// + /// 锁定并返回中间件列表 + /// + function LockMiddlewares: TCrossHttpRouters; + + /// + /// 解锁中间件列表 + /// + procedure UnlockMiddlewares; + + /// + /// 上传文件保存路径 + /// + /// + /// 用于保存multipart/form-data上传的文件 + /// + property StoragePath: string read GetStoragePath write SetStoragePath; + + /// + /// 对象释放时自动删除上传的文件 + /// + property AutoDeleteFiles: Boolean read GetAutoDeleteFiles write SetAutoDeleteFiles; + + /// + /// 最大允许HEADER的数据尺寸 + /// + /// + /// > 0, 限制HEADER尺寸 + /// + /// + /// <= 0, 不限制 + /// + /// + /// + property MaxHeaderSize: Int64 read GetMaxHeaderSize write SetMaxHeaderSize; + + /// + /// 最大允许POST的数据尺寸 + /// + /// + /// > 0, 限制上传数据尺寸 + /// + /// + /// <= 0, 不限制 + /// + /// + /// + property MaxPostDataSize: Int64 read GetMaxPostDataSize write SetMaxPostDataSize; + + /// + /// 是否开启压缩 + /// + /// + /// 开启压缩后, 发往客户端的数据将会进行压缩处理 + /// + property Compressible: Boolean read GetCompressible write SetCompressible; + + /// + /// 最小允许压缩的数据尺寸 + /// + /// + /// + /// + /// 如果设置值大于0, 则只有Body数据尺寸大于等于该值才会进行压缩 + /// + /// + /// 如果设置值小于等于0, 则无视Body数据尺寸, 始终进行压缩 + /// + /// + /// 由于数据是分块压缩发送, 所以数据无论多大都不会占用更多的资源, 也就不需要限制最大压缩尺寸了 + /// + /// + /// 目前支持的压缩方式: gzip, deflate + /// + /// + /// + property MinCompressSize: Int64 read GetMinCompressSize write SetMinCompressSize; + + /// + /// Sessions接口对象 + /// + /// + /// 通过它管理所有Session, 如果不设置则Session功能将不会被启用 + /// + property Sessions: ISessions read GetSessions write SetSessions; + + /// + /// + /// SessionID在Cookie中存储的名称 + /// + /// + /// + /// 如果设置为空, 则Session功能将不会被启用 + /// + property SessionIDCookieName: string read GetSessionIDCookieName write SetSessionIDCookieName; + + property OnRequest: TCrossHttpRequestEvent read GetOnRequest write SetOnRequest; + property OnRequestException: TCrossHttpRequestExceptionEvent read GetOnRequestException write SetOnRequestException; + property OnPostDataBegin: TCrossHttpConnEvent read GetOnPostDataBegin write SetOnPostDataBegin; + property OnPostData: TCrossHttpDataEvent read GetOnPostData write SetOnPostData; + property OnPostDataEnd: TCrossHttpConnEvent read GetOnPostDataEnd write SetOnPostDataEnd; + end; + + TCrossHttpConnection = class({$IFDEF __CROSS_SSL__}TCrossSslConnection{$ELSE}TCrossConnection{$ENDIF}, ICrossHttpConnection) + private + FRequest: ICrossHttpRequest; + FResponse: ICrossHttpResponse; + + function GetRequest: ICrossHttpRequest; + function GetResponse: ICrossHttpResponse; + function GetServer: ICrossHttpServer; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); override; + + property Request: ICrossHttpRequest read GetRequest; + property Response: ICrossHttpResponse read GetResponse; + property Server: ICrossHttpServer read GetServer; + end; + + TCrossHttpRequest = class(TInterfacedObject, ICrossHttpRequest) + private + function GetConnection: ICrossHttpConnection; + function GetRawRequestText: string; + function GetRawPathAndParams: string; + function GetMethod: string; + function GetPath: string; + function GetVersion: string; + function GetHeader: THttpHeader; + function GetCookies: TRequestCookies; + function GetSession: ISession; + function GetParams: THttpUrlParams; + function GetQuery: THttpUrlParams; + function GetBody: TObject; + function GetBodyType: TBodyType; + function GetKeepAlive: Boolean; + function GetAccept: string; + function GetAcceptEncoding: string; + function GetAcceptLanguage: string; + function GetReferer: string; + function GetUserAgent: string; + function GetIfModifiedSince: TDateTime; + function GetIfNoneMatch: string; + function GetRange: string; + function GetIfRange: string; + function GetAuthorization: string; + function GetXForwardedFor: string; + function GetContentLength: Int64; + function GetHostName: string; + function GetHostPort: Word; + function GetContentType: string; + function GetContentEncoding: string; + function GetRequestBoundary: string; + function GetRequestCmdLine: string; + function GetRequestConnection: string; + function GetTransferEncoding: string; + function GetIsChunked: Boolean; + function GetIsMultiPartFormData: Boolean; + function GetIsUrlEncodedFormData: Boolean; + function GetPostDataSize: Int64; + private type + TCrossHttpParseState = (psHeader, psPostData, psChunkSize, psChunkData, psChunkEnd, psDone); + private + FParseState: TCrossHttpParseState; + CR, LF: Integer; + FChunkSizeStream: TBytesStream; + FChunkSize, FChunkLeftSize: Integer; + + FRawRequest: TBytesStream; + FRawRequestText: string; + FMethod, FPath, FVersion: string; + FRawPath, FRawParamsText, FRawPathAndParams: string; + FHttpVerNum: Integer; + FKeepAlive: Boolean; + FAccept: string; + FReferer: string; + FAcceptLanguage: string; + FAcceptEncoding: string; + FUserAgent: string; + FIfModifiedSince: TDateTime; + FIfNoneMatch: string; + FRange: string; + FIfRange: string; + FAuthorization: string; + FXForwardedFor: string; + FContentLength: Int64; + FHostName: string; + FHostPort: Word; + + FPostDataSize: Int64; + + FRequestCmdLine: string; + FContentType: string; + FRequestBoundary: string; + FTransferEncoding: string; + FContentEncoding: string; + FRequestCookies: string; + FRequestHost: string; + FRequestConnection: string; + protected + function ParseRequestData: Boolean; virtual; + private + // Request 是 Connection 的子对象, 它的生命周期跟随 Connection + // 这里使用弱引用, 不增加 Connection 的引用计数, 避免循环引用造成接口对象无法自动释放 + [unsafe]FConnection: ICrossHttpConnection; + FHeader: THttpHeader; + FCookies: TRequestCookies; + FSession: ISession; + FParams: THttpUrlParams; + FQuery: THttpUrlParams; + FBody: TObject; + FBodyType: TBodyType; + public + constructor Create(AConnection: ICrossHttpConnection); + destructor Destroy; override; + + procedure Reset; + + property Connection: ICrossHttpConnection read GetConnection; + property RawRequestText: string read GetRawRequestText; + property RawPathAndParams: string read GetRawPathAndParams; + property Method: string read GetMethod; + property Path: string read GetPath; + property Version: string read GetVersion; + property Header: THttpHeader read GetHeader; + property Cookies: TRequestCookies read GetCookies; + property Session: ISession read GetSession; + property Params: THttpUrlParams read GetParams; + property Query: THttpUrlParams read GetQuery; + property Body: TObject read GetBody; + property BodyType: TBodyType read GetBodyType; + property KeepAlive: Boolean read GetKeepAlive; + property Accept: string read GetAccept; + property AcceptEncoding: string read GetAcceptEncoding; + property AcceptLanguage: string read GetAcceptLanguage; + property Referer: string read GetReferer; + property UserAgent: string read GetUserAgent; + property IfModifiedSince: TDateTime read GetIfModifiedSince; + property IfNoneMatch: string read GetIfNoneMatch; + property Range: string read GetRange; + property IfRange: string read GetIfRange; + property Authorization: string read GetAuthorization; + property XForwardedFor: string read GetXForwardedFor; + property ContentLength: Int64 read GetContentLength; + property HostName: string read GetHostName; + property HostPort: Word read GetHostPort; + property ContentType: string read GetContentType; + + property RequestCmdLine: string read GetRequestCmdLine; + + property RequestBoundary: string read GetRequestBoundary; + property TransferEncoding: string read GetTransferEncoding; + property ContentEncoding: string read GetContentEncoding; + property RequestConnection: string read GetRequestConnection; + property IsChunked: Boolean read GetIsChunked; + property IsMultiPartFormData: Boolean read GetIsMultiPartFormData; + property IsUrlEncodedFormData: Boolean read GetIsUrlEncodedFormData; + property PostDataSize: Int64 read GetPostDataSize; + end; + + TCrossHttpResponse = class(TInterfacedObject, ICrossHttpResponse) + public const + SND_BUF_SIZE = TCrossConnection.SND_BUF_SIZE; + private + // Response 是 Connection 的子对象, 它的生命周期跟随 Connection + // 这里使用弱引用, 不增加 Connection 的引用计数, 避免循环引用造成接口对象无法自动释放 + [unsafe]FConnection: ICrossHttpConnection; + FStatusCode: Integer; + FHeader: THttpHeader; + FCookies: TResponseCookies; + FSendStatus: Integer; + + procedure Reset; + + function GetConnection: ICrossHttpConnection; + function GetRequest: ICrossHttpRequest; + function GetStatusCode: Integer; + procedure SetStatusCode(Value: Integer); + function GetContentType: string; + procedure SetContentType(const Value: string); + function GetLocation: string; + procedure SetLocation(const Value: string); + function GetHeader: THttpHeader; + function GetCookies: TResponseCookies; + function GetSent: Boolean; + + function _CreateHeader(const ABodySize: Int64; AChunked: Boolean): TBytes; + + {$region '内部: 基础发送方法'} + procedure _Send(ASource: TFunc; ACallback: TProc = nil); overload; + procedure _Send(AHeaderSource, ABodySource: TFunc; ACallback: TProc = nil); overload; + {$endregion} + + function _CheckCompress(const ABodySize: Int64; var ACompressType: TCompressType): Boolean; + procedure _AdjustOffsetCount(const ABodySize: NativeInt; var AOffset, ACount: NativeInt); overload; + procedure _AdjustOffsetCount(const ABodySize: Int64; var AOffset, ACount: Int64); overload; + + {$region '压缩发送'} + procedure SendZCompress(const AChunkSource: TFunc; ACompressType: TCompressType; ACallback: TProc = nil); overload; + procedure SendZCompress(const ABody; ACount: NativeInt; ACompressType: TCompressType; ACallback: TProc = nil); overload; + procedure SendZCompress(const ABody: TBytes; AOffset, ACount: NativeInt; ACompressType: TCompressType; ACallback: TProc = nil); overload; + procedure SendZCompress(const ABody: TBytes; ACompressType: TCompressType; ACallback: TProc = nil); overload; + procedure SendZCompress(const ABody: TStream; const AOffset, ACount: Int64; ACompressType: TCompressType; ACallback: TProc = nil); overload; + procedure SendZCompress(const ABody: TStream; ACompressType: TCompressType; ACallback: TProc = nil); overload; + procedure SendZCompress(const ABody: string; ACompressType: TCompressType; ACallback: TProc = nil); overload; + {$endregion} + + {$region '不压缩发送'} + procedure SendNoCompress(const AChunkSource: TFunc; ACallback: TProc = nil); overload; + procedure SendNoCompress(const ABody; ACount: NativeInt; ACallback: TProc = nil); overload; + procedure SendNoCompress(const ABody: TBytes; AOffset, ACount: NativeInt; ACallback: TProc = nil); overload; + procedure SendNoCompress(const ABody: TBytes; ACallback: TProc = nil); overload; + procedure SendNoCompress(const ABody: TStream; const AOffset, ACount: Int64; ACallback: TProc = nil); overload; + procedure SendNoCompress(const ABody: TStream; ACallback: TProc = nil); overload; + procedure SendNoCompress(const ABody: string; ACallback: TProc = nil); overload; + {$endregion} + + {$region '常规方法'} + procedure Send(const ABody; ACount: NativeInt; ACallback: TProc = nil); overload; + procedure Send(const ABody: TBytes; AOffset, ACount: NativeInt; ACallback: TProc = nil); overload; + procedure Send(const ABody: TBytes; ACallback: TProc = nil); overload; + procedure Send(const ABody: TStream; const AOffset, ACount: Int64; ACallback: TProc = nil); overload; + procedure Send(const ABody: TStream; ACallback: TProc = nil); overload; + procedure Send(const ABody: string; ACallback: TProc = nil); overload; + + procedure Json(const AJson: string; ACallback: TProc = nil); + + procedure SendFile(const AFileName: string; ACallback: TProc = nil); + procedure Download(const AFileName: string; ACallback: TProc = nil); + procedure SendStatus(AStatusCode: Integer; const ADescription: string; + ACallback: TProc = nil); overload; + procedure SendStatus(AStatusCode: Integer; + ACallback: TProc = nil); overload; + procedure Redirect(const AUrl: string; ACallback: TProc = nil); + procedure Attachment(const AFileName: string); + {$endregion} + public + constructor Create(AConnection: ICrossHttpConnection); + destructor Destroy; override; + end; + + TCrossHttpRouter = class(TInterfacedObject, ICrossHttpRouter) + private + FMethod, FPath: string; + FRouterProc: TCrossHttpRouterProc; + FRouterMethod: TCrossHttpRouterMethod; + FRouterProc2: TCrossHttpRouterProc2; + FRouterMethod2: TCrossHttpRouterMethod2; + FMethodPattern, FPathPattern: string; + FPathParamKeys: TArray; + FMethodRegEx, FPathRegEx: TPerlRegEx; // 直接使用TPerlRegEx比使用TRegEx速度快1倍 + FRegExLock: TObject; + + function MakeMethodPattern(const AMethod: string): string; + function MakePathPattern(const APath: string; var AKeys: TArray): string; + procedure RemakePattern; + + function GetMethod: string; + function GetPath: string; + public + constructor Create(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; + ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; + ARouterMethod2: TCrossHttpRouterMethod2); + destructor Destroy; override; + + function IsMatch(ARequest: ICrossHttpRequest): Boolean; + procedure Execute(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean); + end; + + TCrossHttpServer = class({$IFDEF __CROSS_SSL__}TCrossSslServer{$ELSE}TCrossServer{$ENDIF}, ICrossHttpServer) + private const + HTTP_METHOD_COUNT = 16; + HTTP_METHODS: array [0..HTTP_METHOD_COUNT-1] of string = ( + 'GET', 'POST', 'PUT', 'DELETE', + 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT', + 'PATCH', 'COPY', 'LINK', 'UNLINK', + 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND'); + SESSIONID_COOKIE_NAME = 'cross_sessionid'; + private + FMethodTags: array [0..HTTP_METHOD_COUNT-1] of TBytes; + FStoragePath: string; + FAutoDeleteFiles: Boolean; + FMaxPostDataSize: Int64; + FMaxHeaderSize: Int64; + FMinCompressSize: Integer; + FSessionIDCookieName: string; + FRouters: TCrossHttpRouters; + FRoutersLock: TMultiReadExclusiveWriteSynchronizer; + FMiddlewares: TCrossHttpRouters; + FMiddlewaresLock: TMultiReadExclusiveWriteSynchronizer; + FSessions: ISessions; + FOnRequest: TCrossHttpRequestEvent; + FOnRequestException: TCrossHttpRequestExceptionEvent; + FOnPostDataBegin: TCrossHttpConnEvent; + FOnPostData: TCrossHttpDataEvent; + FOnPostDataEnd: TCrossHttpConnEvent; + FCompressible: Boolean; + + function IsValidHttpRequest(ABuf: Pointer; ALen: Integer): Boolean; + procedure ParseRecvData(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); + + function RegisterRouter(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; + ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; + ARouterMethod2: TCrossHttpRouterMethod2): TCrossHttpServer; + function RegisterMiddleware(const AMethod, APath: string; + AMiddlewareProc: TCrossHttpRouterProc; + AMiddlewareMethod: TCrossHttpRouterMethod; + AMiddlewareProc2: TCrossHttpRouterProc2; + AMiddlewareMethod2: TCrossHttpRouterMethod2): TCrossHttpServer; + private + function GetStoragePath: string; + function GetAutoDeleteFiles: Boolean; + function GetMaxHeaderSize: Int64; + function GetMaxPostDataSize: Int64; + function GetCompressible: Boolean; + function GetMinCompressSize: Int64; + function GetSessions: ISessions; + function GetSessionIDCookieName: string; + function GetOnRequest: TCrossHttpRequestEvent; + function GetOnRequestException: TCrossHttpRequestExceptionEvent; + function GetOnPostDataBegin: TCrossHttpConnEvent; + function GetOnPostData: TCrossHttpDataEvent; + function GetOnPostDataEnd: TCrossHttpConnEvent; + + procedure SetStoragePath(const Value: string); + procedure SetAutoDeleteFiles(const Value: Boolean); + procedure SetMaxHeaderSize(const Value: Int64); + procedure SetMaxPostDataSize(const Value: Int64); + procedure SetCompressible(const Value: Boolean); + procedure SetMinCompressSize(const Value: Int64); + procedure SetSessions(const Value: ISessions); + procedure SetSessionIDCookieName(const Value: string); + procedure SetOnRequest(const Value: TCrossHttpRequestEvent); + procedure SetOnRequestException(const Value: TCrossHttpRequestExceptionEvent); + procedure SetOnPostDataBegin(const Value: TCrossHttpConnEvent); + procedure SetOnPostData(const Value: TCrossHttpDataEvent); + procedure SetOnPostDataEnd(const Value: TCrossHttpConnEvent); + protected + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + + function CreateRouter(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpRouter; virtual; + + procedure LogicReceived(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); override; + protected + procedure TriggerPostDataBegin(AConnection: ICrossHttpConnection); virtual; + procedure TriggerPostData(AConnection: ICrossHttpConnection; + const ABuf: Pointer; ALen: Integer); virtual; + procedure TriggerPostDataEnd(AConnection: ICrossHttpConnection); virtual; + + // 处理请求 + procedure DoOnRequest(AConnection: ICrossHttpConnection); virtual; + public + constructor Create(AIoThreads: Integer); override; + destructor Destroy; override; + + function Use(const AMethod, APath: string; + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + + function Use(const AMethod, APath: string; + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + function Use(const AMethod, APath: string; + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + + function Use(const AMethod, APath: string; + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Use(const APath: string; + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + + function Use(const APath: string; + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + + function Use(const APath: string; + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + + function Use(const APath: string; + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Use(AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Use(AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function Use(AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Use(AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Route(const AMethod, APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Route(const AMethod, APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function Route(const AMethod, APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Route(const AMethod, APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Get(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Get(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function Get(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Get(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Put(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Put(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function Put(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Put(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Post(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Post(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function Post(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Post(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function Delete(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function Delete(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function Delete(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function Delete(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function All(const APath: string; ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; overload; + function All(const APath: string; ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; overload; + function All(const APath: string; ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; overload; + function All(const APath: string; ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; overload; + + function &Static(const APath, ALocalStaticDir: string): ICrossHttpServer; + function Dir(const APath, ALocalDir: string): ICrossHttpServer; + function Index(const APath, ALocalDir: string; const ADefIndexFiles: TArray): ICrossHttpServer; + + function RemoveRouter(const AMethod, APath: string): ICrossHttpServer; + function ClearRouter: ICrossHttpServer; + + function LockRouters: TCrossHttpRouters; + procedure UnlockRouters; + + function LockMiddlewares: TCrossHttpRouters; + procedure UnlockMiddlewares; + + property StoragePath: string read GetStoragePath write SetStoragePath; + property AutoDeleteFiles: Boolean read GetAutoDeleteFiles write SetAutoDeleteFiles; + property MaxHeaderSize: Int64 read GetMaxHeaderSize write SetMaxHeaderSize; + property MaxPostDataSize: Int64 read GetMaxPostDataSize write SetMaxPostDataSize; + property Compressible: Boolean read GetCompressible write SetCompressible; + property MinCompressSize: Int64 read GetMinCompressSize write SetMinCompressSize; + property Sessions: ISessions read GetSessions write SetSessions; + property SessionIDCookieName: string read GetSessionIDCookieName write SetSessionIDCookieName; + + property OnRequest: TCrossHttpRequestEvent read GetOnRequest write SetOnRequest; + property OnRequestException: TCrossHttpRequestExceptionEvent read GetOnRequestException write SetOnRequestException; + property OnPostDataBegin: TCrossHttpConnEvent read GetOnPostDataBegin write SetOnPostDataBegin; + property OnPostData: TCrossHttpDataEvent read GetOnPostData write SetOnPostData; + property OnPostDataEnd: TCrossHttpConnEvent read GetOnPostDataEnd write SetOnPostDataEnd; + end; + +implementation + +uses + {$IFDEF MSWINDOWS} + Winapi.Windows, + {$ENDIF} + Utils.RegEx, Utils.Utils, + Net.CrossHttpRouter; + + +{ ECrossHttpException } + +constructor ECrossHttpException.Create(const AMessage: string; + AStatusCode: Integer); +begin + inherited Create(AMessage); + FStatusCode := AStatusCode; +end; + +constructor ECrossHttpException.CreateFmt(const AMessage: string; + const AArgs: array of const; AStatusCode: Integer); +begin + inherited CreateFmt(AMessage, AArgs); + FStatusCode := AStatusCode; +end; + +{ TCrossHttpConnection } + +constructor TCrossHttpConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +var + LConnection: ICrossHttpConnection; +begin + inherited; + + LConnection := Self; + + FRequest := TCrossHttpRequest.Create(LConnection); + FResponse := TCrossHttpResponse.Create(LConnection); +end; + +function TCrossHttpConnection.GetRequest: ICrossHttpRequest; +begin + Result := FRequest; +end; + +function TCrossHttpConnection.GetResponse: ICrossHttpResponse; +begin + Result := FResponse; +end; + +function TCrossHttpConnection.GetServer: ICrossHttpServer; +begin + Result := Owner as ICrossHttpServer; +end; + +{ TCrossHttpRouter } + +constructor TCrossHttpRouter.Create(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; ARouterMethod2: TCrossHttpRouterMethod2); +begin + FMethod := AMethod; + FPath := APath; + FRouterProc := ARouterProc; + FRouterMethod := ARouterMethod; + FRouterProc2 := ARouterProc2; + FRouterMethod2 := ARouterMethod2; + + FMethodRegEx := TPerlRegEx.Create; + FMethodRegEx.Options := [preCaseLess]; + + FPathRegEx := TPerlRegEx.Create; + FPathRegEx.Options := [preCaseLess]; + + FRegExLock := TObject.Create; + + RemakePattern; +end; + +destructor TCrossHttpRouter.Destroy; +begin + TMonitor.Enter(FRegExLock); + try + FreeAndNil(FMethodRegEx); + FreeAndNil(FPathRegEx); + finally + TMonitor.Exit(FRegExLock); + end; + FreeAndNil(FRegExLock); + + inherited; +end; + +function TCrossHttpRouter.MakeMethodPattern(const AMethod: string): string; +var + LPattern: string; +begin + LPattern := AMethod; + + // 通配符*转正则表达式 + LPattern := TRegEx.Replace(LPattern, '(?): string; +var + LPattern: string; + LKeys: TArray; +begin + LKeys := []; + LPattern := APath; + + // 最后增加 /? + if APath.EndsWith('/') then + LPattern := LPattern + '?' + else + LPattern := LPattern + '/?'; + + // 将 /( 替换成 /(?: + LPattern := TRegEx.Replace(LPattern, '\/\(', '/(?:'); + + // 将 / . 替换成 \/ \. + LPattern := TRegEx.Replace(LPattern, '([\/\.])', '\\$1'); + + // 提取形如 :keyname 的参数名称 + // 可以在参数后面增加正则限定参数 :number(\d+), :word(\w+) + LPattern := TRegEx.Replace(LPattern, '(\\\/)?(\\\.)?:(\w+)(\(.*?\))?(\*)?(\?)?', + function(const Match: TMatch): string + var + LSlash, LFormat, LKey, LCapture, LStar, LOptional: string; + begin + if not Match.Success then Exit(''); + + if (Match.Groups.Count > 1) then + LSlash := Match.Groups[1].Value + else + LSlash := ''; + if (Match.Groups.Count > 2) then + LFormat := Match.Groups[2].Value + else + LFormat := ''; + if (Match.Groups.Count > 3) then + LKey := Match.Groups[3].Value + else + LKey := ''; + if (Match.Groups.Count > 4) then + LCapture := Match.Groups[4].Value + else + LCapture := ''; + if (Match.Groups.Count > 5) then + LStar := Match.Groups[5].Value + else + LStar := ''; + if (Match.Groups.Count > 6) then + LOptional := Match.Groups[6].Value + else + LOptional := ''; + + if (LCapture = '') then + LCapture := '([^\\/' + LFormat + ']+?)'; + + Result := ''; + if (LOptional = '') then + Result := Result + LSlash; + Result := Result + '(?:' + LFormat; + if (LOptional <> '') then + Result := Result + LSlash; + Result := Result + LCapture; + if (LStar <> '') then + Result := Result + '((?:[\\/' + LFormat + '].+?)?)'; + Result := Result + ')' + LOptional; + + LKeys := LKeys + [LKey]; + end); + + // 通配符*转正则表达式 + LPattern := TRegEx.Replace(LPattern, '(? '/') then + FPath := '/' + FPath; + + FMethodPattern := MakeMethodPattern(FMethod); + FPathPattern := MakePathPattern(FPath, FPathParamKeys); + + TMonitor.Enter(FRegExLock); + try + FMethodRegEx.RegEx := FMethodPattern; + FPathRegEx.RegEx := FPathPattern; + finally + TMonitor.Exit(FRegExLock); + end; +end; + +function TCrossHttpRouter.IsMatch(ARequest: ICrossHttpRequest): Boolean; + function _IsMatchMethod: Boolean; + begin + // Method中不包括参数, 使用SameText辅助加速 + if (FMethod = '*') or SameText(ARequest.Method, FMethod) then Exit(True); + + FMethodRegEx.Subject := ARequest.Method; + Result := FMethodRegEx.Match; + end; + + function _IsMatchPath: Boolean; + var + I: Integer; + begin + // Path中不包括参数时, 使用SameText辅助加速 + if (FPath = '*') or ((Length(FPathParamKeys) = 0) and SameText(ARequest.Path, FPath)) then Exit(True); + + FPathRegEx.Subject := ARequest.Path; + Result := FPathRegEx.Match; + if not Result then Exit; + + // 将Path中的参数解析出来保存到Request.Params中 + // 注意: TPerlRegEx.GroupCount 是实际 GroupCount - 1 + for I := 1 to FPathRegEx.GroupCount do + ARequest.Params[FPathParamKeys[I - 1]] := FPathRegEx.Groups[I]; + end; +begin + ARequest.Params.Clear; + + TMonitor.Enter(FRegExLock); + try + Result := _IsMatchMethod and _IsMatchPath; + finally + TMonitor.Exit(FRegExLock); + end; +end; + +procedure TCrossHttpRouter.Execute(ARequest: ICrossHttpRequest; AResponse: ICrossHttpResponse; var AHandled: Boolean); +begin + if Assigned(FRouterProc) then + begin + FRouterProc(ARequest, AResponse); + end else + if Assigned(FRouterMethod) then + begin + FRouterMethod(ARequest, AResponse); + end else + if Assigned(FRouterProc2) then + begin + FRouterProc2(ARequest, AResponse, AHandled); + end else + if Assigned(FRouterMethod2) then + begin + FRouterMethod2(ARequest, AResponse, AHandled); + end; +end; + +function TCrossHttpRouter.GetMethod: string; +begin + Result := FMethod; +end; + +function TCrossHttpRouter.GetPath: string; +begin + Result := FPath; +end; + +{ TCrossHttpServer } + +function TCrossHttpServer.All(const APath: string; + ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Route('*', APath, ARouterProc); +end; + +function TCrossHttpServer.All(const APath: string; + ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Route('*', APath, ARouterProc2); +end; + +function TCrossHttpServer.All(const APath: string; + ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Route('*', APath, ARouterMethod); +end; + +function TCrossHttpServer.All(const APath: string; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Route('*', APath, ARouterMethod2); +end; + +constructor TCrossHttpServer.Create(AIoThreads: Integer); +var + I: Integer; +begin + inherited Create(AIoThreads); + + FRouters := TCrossHttpRouters.Create; + FRoutersLock := TMultiReadExclusiveWriteSynchronizer.Create; + + FMiddlewares := TCrossHttpRouters.Create; + FMiddlewaresLock := TMultiReadExclusiveWriteSynchronizer.Create; + + for I := Low(FMethodTags) to High(FMethodTags) do + FMethodTags[I] := TEncoding.ANSI.GetBytes(HTTP_METHODS[I]); + + Port := 80; + Addr := ''; + + FCompressible := True; + FMinCompressSize := MIN_COMPRESS_SIZE; + FStoragePath := TPath.Combine(TUtils.AppPath, 'temp') + TPath.DirectorySeparatorChar; + FSessionIDCookieName := SESSIONID_COOKIE_NAME; +end; + +function TCrossHttpServer.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TCrossHttpConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +function TCrossHttpServer.CreateRouter(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpRouter; +begin + Result := TCrossHttpRouter.Create(AMethod, APath, + ARouterProc, ARouterMethod, + ARouterProc2, ARouterMethod2); +end; + +destructor TCrossHttpServer.Destroy; +begin + Stop; + + FRoutersLock.BeginWrite; + FreeAndNil(FRouters); + FRoutersLock.EndWrite; + FreeAndNil(FRoutersLock); + + FMiddlewaresLock.BeginWrite; + FreeAndNil(FMiddlewares); + FMiddlewaresLock.EndWrite; + FreeAndNil(FMiddlewaresLock); + + inherited Destroy; +end; + +function TCrossHttpServer.Dir(const APath, ALocalDir: string): ICrossHttpServer; +var + LReqPath: string; +begin + LReqPath := APath; + if not LReqPath.EndsWith('/') then + LReqPath := LReqPath + '/'; + LReqPath := LReqPath + '?:dir(*)'; + Result := Get(LReqPath, TNetCrossRouter.Dir(APath, ALocalDir, 'dir')); +end; + +function TCrossHttpServer.Delete(const APath: string; + ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Route('DELETE', APath, ARouterProc); +end; + +function TCrossHttpServer.Delete(const APath: string; + ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Route('DELETE', APath, ARouterProc2); +end; + +function TCrossHttpServer.Delete(const APath: string; + ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Route('DELETE', APath, ARouterMethod); +end; + +function TCrossHttpServer.Delete(const APath: string; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Route('DELETE', APath, ARouterMethod2); +end; + +procedure TCrossHttpServer.DoOnRequest(AConnection: ICrossHttpConnection); +var + LRequest: ICrossHttpRequest; + LResponse: ICrossHttpResponse; + LSessionID: string; + LHandled: Boolean; + LRouter, LMiddleware: ICrossHttpRouter; + LMiddlewares, LRouters: TArray; +begin + LRequest := AConnection.Request; + LResponse := AConnection.Response; + + try + {$region 'Session'} + if (FSessions <> nil) and (FSessionIDCookieName <> '') then + begin + LSessionID := LRequest.Cookies[FSessionIDCookieName]; + (LRequest as TCrossHttpRequest).FSession := FSessions.Sessions[LSessionID]; + if (LRequest.Session <> nil) and (LRequest.Session.SessionID <> LSessionID) then + begin + LSessionID := LRequest.Session.SessionID; + LResponse.Cookies.AddOrSet(FSessionIDCookieName, LSessionID, 0); + end; + end; + {$endregion} + + {$region '中间件'} + FMiddlewaresLock.BeginRead; + try + // 先将中间件保存到临时数组中 + // 然后再执行中间件 + // 这样做是为了减少加锁的时间 + LMiddlewares := FMiddlewares.ToArray; + finally + FMiddlewaresLock.EndRead; + end; + for LMiddleware in LMiddlewares do + begin + // 执行匹配的中间件 + if LMiddleware.IsMatch(LRequest) then + begin + // 中间件通常用于请求的预处理 + // 所以默认将 LHandled 置为 False, 以保证后续路由能被执行 + // 除非用户在中间件中明确指定了 LHandled := True, 表明该请求无需后续路由响应了 + LHandled := False; + LMiddleware.Execute(LRequest, LResponse, LHandled); + + // 如果已经发送了数据, 则后续的事件和路由响应都不需要执行了 + if LHandled or LResponse.Sent then Exit; + end; + end; + {$endregion} + + {$region '响应请求事件'} + if Assigned(FOnRequest) then + begin + LHandled := False; + FOnRequest(Self, LRequest, LResponse, LHandled); + + // 如果已经发送了数据, 则后续的事件和路由响应都不需要执行了 + if LHandled or LResponse.Sent then Exit; + end; + {$endregion} + + {$region '路由'} + FRoutersLock.BeginRead; + try + // 先将路由保存到临时数组中 + // 然后再执行路由 + // 这样做是为了减少加锁时间 + LRouters := FRouters.ToArray; + finally + FRoutersLock.EndRead; + end; + for LRouter in LRouters do + begin + // 执行匹配的路由 + if LRouter.IsMatch(LRequest) then + begin + // 路由用于响应请求 + // 所以默认将 LHandled 置为 True, 以保证不会有多个匹配的路由被执行 + // 除非用户在路由中明确指定了 LHandled := False, 表明该路由并没有 + // 完成请求响应, 还需要后续路由继续进行响应 + LHandled := True; + LRouter.Execute(LRequest, LResponse, LHandled); + + // 如果已经发送了数据, 则后续的事件和路由响应都不需要执行了 + if LHandled or LResponse.Sent then Exit; + end; + end; + {$endregion} + + // 如果该请求没有被任何中间件、事件、路由响应, 返回 404 + if not (LHandled or LResponse.Sent) then + LResponse.SendStatus(404); + except + on e: Exception do + begin + if Assigned(FOnRequestException) then + FOnRequestException(Self, LRequest, LResponse, e) + else if (e is ECrossHttpException) then + LResponse.SendStatus(ECrossHttpException(e).StatusCode, ECrossHttpException(e).Message) + else + LResponse.SendStatus(500, e.Message); + end; + end; +end; + +function TCrossHttpServer.Get(const APath: string; + ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Route('GET', APath, ARouterProc); +end; + +function TCrossHttpServer.Get(const APath: string; + ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Route('GET', APath, ARouterProc2); +end; + +function TCrossHttpServer.Get(const APath: string; + ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Route('GET', APath, ARouterMethod); +end; + +function TCrossHttpServer.Get(const APath: string; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Route('GET', APath, ARouterMethod2); +end; + +function TCrossHttpServer.GetAutoDeleteFiles: Boolean; +begin + Result := FAutoDeleteFiles; +end; + +function TCrossHttpServer.GetCompressible: Boolean; +begin + Result := FCompressible; +end; + +function TCrossHttpServer.GetMaxHeaderSize: Int64; +begin + Result := FMaxHeaderSize; +end; + +function TCrossHttpServer.GetMaxPostDataSize: Int64; +begin + Result := FMaxPostDataSize; +end; + +function TCrossHttpServer.GetMinCompressSize: Int64; +begin + Result := FMinCompressSize; +end; + +function TCrossHttpServer.GetOnPostData: TCrossHttpDataEvent; +begin + Result := FOnPostData; +end; + +function TCrossHttpServer.GetOnPostDataBegin: TCrossHttpConnEvent; +begin + Result := FOnPostDataBegin; +end; + +function TCrossHttpServer.GetOnPostDataEnd: TCrossHttpConnEvent; +begin + Result := FOnPostDataEnd; +end; + +function TCrossHttpServer.GetOnRequest: TCrossHttpRequestEvent; +begin + Result := FOnRequest; +end; + +function TCrossHttpServer.GetOnRequestException: TCrossHttpRequestExceptionEvent; +begin + Result := FOnRequestException; +end; + +function TCrossHttpServer.GetSessionIDCookieName: string; +begin + Result := FSessionIDCookieName; +end; + +function TCrossHttpServer.GetSessions: ISessions; +begin + Result := FSessions; +end; + +function TCrossHttpServer.GetStoragePath: string; +begin + Result := FStoragePath; +end; + +procedure TCrossHttpServer.SetAutoDeleteFiles(const Value: Boolean); +begin + FAutoDeleteFiles := Value; +end; + +procedure TCrossHttpServer.SetCompressible(const Value: Boolean); +begin + FCompressible := Value; +end; + +procedure TCrossHttpServer.SetMaxHeaderSize(const Value: Int64); +begin + FMaxHeaderSize := Value; +end; + +procedure TCrossHttpServer.SetMaxPostDataSize(const Value: Int64); +begin + FMaxPostDataSize := Value; +end; + +procedure TCrossHttpServer.SetMinCompressSize(const Value: Int64); +begin + FMinCompressSize := Value; +end; + +procedure TCrossHttpServer.SetOnPostData(const Value: TCrossHttpDataEvent); +begin + FOnPostData := Value; +end; + +procedure TCrossHttpServer.SetOnPostDataBegin(const Value: TCrossHttpConnEvent); +begin + FOnPostDataBegin := Value; +end; + +procedure TCrossHttpServer.SetOnPostDataEnd(const Value: TCrossHttpConnEvent); +begin + FOnPostDataEnd := Value; +end; + +procedure TCrossHttpServer.SetOnRequest(const Value: TCrossHttpRequestEvent); +begin + FOnRequest := Value; +end; + +procedure TCrossHttpServer.SetOnRequestException( + const Value: TCrossHttpRequestExceptionEvent); +begin + FOnRequestException := Value; +end; + +procedure TCrossHttpServer.SetSessionIDCookieName(const Value: string); +begin + FSessionIDCookieName := Value; +end; + +procedure TCrossHttpServer.SetSessions(const Value: ISessions); +begin + FSessions := Value; +end; + +procedure TCrossHttpServer.SetStoragePath(const Value: string); +begin + FStoragePath := Value; +end; + +function TCrossHttpServer.Static(const APath, + ALocalStaticDir: string): ICrossHttpServer; +var + LReqPath: string; +begin + LReqPath := APath; + if not LReqPath.EndsWith('/') then + LReqPath := LReqPath + '/'; + LReqPath := LReqPath + ':file(*)'; + Result := Get(LReqPath, TNetCrossRouter.Static(ALocalStaticDir, 'file')); +end; + +function TCrossHttpServer.Index(const APath, ALocalDir: string; + const ADefIndexFiles: TArray): ICrossHttpServer; +var + LReqPath: string; +begin + LReqPath := APath; + if not LReqPath.EndsWith('/') then + LReqPath := LReqPath + '/'; + LReqPath := LReqPath + ':file(*)'; + Result := Get(LReqPath, TNetCrossRouter.Index(ALocalDir, 'file', ADefIndexFiles)); +end; + +function TCrossHttpServer.IsValidHttpRequest(ABuf: Pointer; + ALen: Integer): Boolean; +var + LBytes: TBytes; + I: Integer; +begin + for I := Low(FMethodTags) to High(FMethodTags) do + begin + LBytes := FMethodTags[I]; + if (ALen >= Length(LBytes)) and + CompareMem(ABuf, Pointer(LBytes), Length(LBytes)) then Exit(True); + end; + Result := False; +end; + +procedure TCrossHttpServer.ParseRecvData(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +var + LHttpConnection: ICrossHttpConnection; + LRequest: TCrossHttpRequest; + LResponse: TCrossHttpResponse; + pch: PByte; + LChunkSize: Integer; + LLineStr: string; + + procedure _Error(AStatusCode: Integer; const AErrMsg: string); + begin + LHttpConnection.Response.SendStatus(AStatusCode, AErrMsg); + end; + +begin + LHttpConnection := AConnection as ICrossHttpConnection; + LRequest := LHttpConnection.Request as TCrossHttpRequest; + LResponse := LHttpConnection.Response as TCrossHttpResponse; + + // 在这里解析客户端浏览器发送过来的请求数据 + if (LRequest.FParseState = psDone) then + begin + LRequest.Reset; + LResponse.Reset; + end; + + pch := ABuf; + while (ALen > 0) do + begin + case LRequest.FParseState of + psHeader: + begin + case pch^ of + 13{\r}: Inc(LRequest.CR); + 10{\n}: Inc(LRequest.LF); + else + LRequest.CR := 0; + LRequest.LF := 0; + end; + + // Header尺寸超标 + if (FMaxHeaderSize > 0) and (LRequest.FRawRequest.Size + 1 > FMaxHeaderSize) then + begin + _Error(400, 'Request header too large.'); + Exit; + end; + + // 写入请求数据 + LRequest.FRawRequest.Write(pch^, 1); + Dec(ALen); + Inc(pch); + + // 如果不是有效的Http请求直接断开 + // HTTP 请求命令中最长的命令是 PROPFIND, 长度为8个字符 + // 所以在收到8个字节的时候进行检测 + if (LRequest.FRawRequest.Size = 8) and + not IsValidHttpRequest(LRequest.FRawRequest.Memory, LRequest.FRawRequest.Size) then + begin + _Error(400, 'Request method invalid.'); + Exit; + end; + + // HTTP头已接收完毕(\r\n\r\n是HTTP头结束的标志) + if (LRequest.CR = 2) and (LRequest.LF = 2) then + begin + LRequest.CR := 0; + LRequest.LF := 0; + + if not LRequest.ParseRequestData then + begin + _Error(400, 'Request data invalid.'); + Exit; + end; + + // Post数据尺寸超标, 直接断开连接 + if (FMaxPostDataSize > 0) and (LRequest.FContentLength > FMaxPostDataSize) then + begin + _Error(400, 'Post data too large.'); + Exit; + end; + + // 如果 RequestContentLength 大于 0, 或者是 Chunked 编码, 则还需要接收 post 数据 + if (LRequest.FContentLength > 0) or LRequest.IsChunked then + begin + LRequest.FPostDataSize := 0; + + if LRequest.IsChunked then + begin + LRequest.FParseState := psChunkSize; + LRequest.FChunkSizeStream := TBytesStream.Create(nil); + end else + LRequest.FParseState := psPostData; + + TriggerPostDataBegin(LHttpConnection); + end else + begin + LRequest.FBodyType := btNone; + LRequest.FParseState := psDone; + Break; + end; + end; + end; + + // 非Chunked编码的Post数据(有RequestContentLength) + psPostData: + begin + LChunkSize := Min((LRequest.ContentLength - LRequest.FPostDataSize), ALen); + // Post数据尺寸超标, 直接断开连接 + if (FMaxPostDataSize > 0) and (LRequest.FPostDataSize + LChunkSize > FMaxPostDataSize) then + begin + _Error(400, 'Post data too large.'); + Exit; + end; + TriggerPostData(LHttpConnection, pch, LChunkSize); + + Inc(LRequest.FPostDataSize, LChunkSize); + Inc(pch, LChunkSize); + Dec(ALen, LChunkSize); + + if (LRequest.FPostDataSize >= LRequest.ContentLength) then + begin + LRequest.FParseState := psDone; + TriggerPostDataEnd(LHttpConnection); + Break; + end; + end; + + // Chunked编码: 块尺寸 + psChunkSize: + begin + case pch^ of + 13{\r}: Inc(LRequest.CR); + 10{\n}: Inc(LRequest.LF); + else + LRequest.CR := 0; + LRequest.LF := 0; + LRequest.FChunkSizeStream.Write(pch^, 1); + end; + Dec(ALen); + Inc(pch); + + if (LRequest.CR = 1) and (LRequest.LF = 1) then + begin + SetString(LLineStr, MarshaledAString(LRequest.FChunkSizeStream.Memory), LRequest.FChunkSizeStream.Size); + LRequest.FParseState := psChunkData; + LRequest.FChunkSize := StrToIntDef('$' + Trim(LLineStr), -1); + LRequest.FChunkLeftSize := LRequest.FChunkSize; + end; + end; + + // Chunked编码: 块数据 + psChunkData: + begin + if (LRequest.FChunkLeftSize > 0) then + begin + LChunkSize := Min(LRequest.FChunkLeftSize, ALen); + // Post数据尺寸超标, 直接断开连接 + if (FMaxPostDataSize > 0) and (LRequest.FPostDataSize + LChunkSize > FMaxPostDataSize) then + begin + _Error(400, 'Post data too large.'); + Exit; + end; + TriggerPostData(LHttpConnection, pch, LChunkSize); + + Inc(LRequest.FPostDataSize, LChunkSize); + Dec(LRequest.FChunkLeftSize, LChunkSize); + Inc(pch, LChunkSize); + Dec(ALen, LChunkSize); + end; + + if (LRequest.FChunkLeftSize <= 0) then + begin + LRequest.FParseState := psChunkEnd; + LRequest.CR := 0; + LRequest.LF := 0; + end; + end; + + // Chunked编码: 块结束符\r\n + psChunkEnd: + begin + case pch^ of + 13{\r}: Inc(LRequest.CR); + 10{\n}: Inc(LRequest.LF); + else + LRequest.CR := 0; + LRequest.LF := 0; + end; + Dec(ALen); + Inc(pch); + + if (LRequest.CR = 1) and (LRequest.LF = 1) then + begin + // 最后一块的ChunSize为0 + if (LRequest.FChunkSize > 0) then + begin + LRequest.FParseState := psChunkSize; + LRequest.FChunkSizeStream.Clear; + LRequest.CR := 0; + LRequest.LF := 0; + end else + begin + LRequest.FParseState := psDone; + FreeAndNil(LRequest.FChunkSizeStream); + TriggerPostDataEnd(LHttpConnection); + Break; + end; + end; + end; + end; + end; + + // 处理请求 + if (LRequest.FParseState = psDone) then + DoOnRequest(LHttpConnection); + + // 处理粘包 + if (ALen > 0) then + ParseRecvData(LHttpConnection, pch, ALen); +end; + +function TCrossHttpServer.Post(const APath: string; + ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Route('POST', APath, ARouterProc); +end; + +function TCrossHttpServer.Post(const APath: string; + ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Route('POST', APath, ARouterProc2); +end; + +function TCrossHttpServer.Post(const APath: string; + ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Route('POST', APath, ARouterMethod); +end; + +function TCrossHttpServer.Post(const APath: string; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Route('POST', APath, ARouterMethod2); +end; + +function TCrossHttpServer.Put(const APath: string; + ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Route('PUT', APath, ARouterMethod); +end; + +function TCrossHttpServer.Put(const APath: string; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Route('PUT', APath, ARouterMethod2); +end; + +function TCrossHttpServer.Put(const APath: string; + ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Route('PUT', APath, ARouterProc); +end; + +function TCrossHttpServer.Put(const APath: string; + ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Route('PUT', APath, ARouterProc2); +end; + +function TCrossHttpServer.RegisterMiddleware(const AMethod, APath: string; + AMiddlewareProc: TCrossHttpRouterProc; + AMiddlewareMethod: TCrossHttpRouterMethod; + AMiddlewareProc2: TCrossHttpRouterProc2; + AMiddlewareMethod2: TCrossHttpRouterMethod2): TCrossHttpServer; +var + LMiddleware: ICrossHttpRouter; +begin + LMiddleware := CreateRouter(AMethod, APath, + AMiddlewareProc, AMiddlewareMethod, + AMiddlewareProc2, AMiddlewareMethod2); + FMiddlewaresLock.BeginWrite; + try + FMiddlewares.Add(LMiddleware); + finally + FMiddlewaresLock.EndWrite; + end; + Result := Self; +end; + +function TCrossHttpServer.RegisterRouter(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc; ARouterMethod: TCrossHttpRouterMethod; + ARouterProc2: TCrossHttpRouterProc2; ARouterMethod2: TCrossHttpRouterMethod2): TCrossHttpServer; +var + LRouter: ICrossHttpRouter; +begin + LRouter := CreateRouter(AMethod, APath, + ARouterProc, ARouterMethod, + ARouterProc2, ARouterMethod2); + FRoutersLock.BeginWrite; + try + FRouters.Add(LRouter); + finally + FRoutersLock.EndWrite; + end; + Result := Self; +end; + +function TCrossHttpServer.Route(const AMethod, APath: string; + ARouterProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := RegisterRouter(AMethod, APath, ARouterProc, nil, nil, nil); +end; + +function TCrossHttpServer.Route(const AMethod, APath: string; + ARouterProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := RegisterRouter(AMethod, APath, nil, nil, ARouterProc2, nil); +end; + +function TCrossHttpServer.Route(const AMethod, APath: string; + ARouterMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := RegisterRouter(AMethod, APath, nil, ARouterMethod, nil, nil); +end; + +function TCrossHttpServer.Route(const AMethod, APath: string; + ARouterMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := RegisterRouter(AMethod, APath, nil, nil, nil, ARouterMethod2); +end; + +function TCrossHttpServer.RemoveRouter(const AMethod, APath: string): ICrossHttpServer; +var + I: Integer; +begin + FRoutersLock.BeginWrite; + try + for I := FRouters.Count - 1 downto 0 do + if SameText(FRouters[I].Method, AMethod) and SameText(FRouters[I].Path, APath) then + FRouters.Delete(I); + finally + FRoutersLock.EndWrite; + end; + Result := Self; +end; + +function TCrossHttpServer.ClearRouter: ICrossHttpServer; +begin + FRoutersLock.BeginWrite; + try + FRouters.Clear; + finally + FRoutersLock.EndWrite; + end; + Result := Self; +end; + +procedure TCrossHttpServer.TriggerPostDataBegin( + AConnection: ICrossHttpConnection); +var + LRequest: TCrossHttpRequest; + LMultiPart: THttpMultiPartFormData; + LStream: TStream; +begin + LRequest := AConnection.Request as TCrossHttpRequest; + case LRequest.BodyType of + btMultiPart: + begin + if (FStoragePath <> '') and not TDirectory.Exists(FStoragePath) then + TDirectory.CreateDirectory(FStoragePath); + + LMultiPart := THttpMultiPartFormData.Create; + LMultiPart.StoragePath := FStoragePath; + LMultiPart.AutoDeleteFiles := FAutoDeleteFiles; + LMultiPart.InitWithBoundary(LRequest.RequestBoundary); + FreeAndNil(LRequest.FBody); + LRequest.FBody := LMultiPart; + end; + + btUrlEncoded: + begin + LStream := TBytesStream.Create; + FreeAndNil(LRequest.FBody); + LRequest.FBody := LStream; + end; + + btBinary: + begin + LStream := TBytesStream.Create(nil); + FreeAndNil(LRequest.FBody); + LRequest.FBody := LStream; + end; + end; + + if Assigned(FOnPostDataBegin) then + FOnPostDataBegin(Self, AConnection); +end; + +procedure TCrossHttpServer.TriggerPostData(AConnection: ICrossHttpConnection; + const ABuf: Pointer; ALen: Integer); +var + LRequest: TCrossHttpRequest; +begin + LRequest := AConnection.Request as TCrossHttpRequest; + + case LRequest.GetBodyType of + btMultiPart: (LRequest.Body as THttpMultiPartFormData).Decode(ABuf, ALen); + btUrlEncoded: (LRequest.Body as TStream).Write(ABuf^, ALen); + btBinary: (LRequest.Body as TStream).Write(ABuf^, ALen); + end; + + if Assigned(FOnPostData) then + FOnPostData(Self, AConnection, ABuf, ALen); +end; + +procedure TCrossHttpServer.TriggerPostDataEnd( + AConnection: ICrossHttpConnection); +var + LRequest: TCrossHttpRequest; + LUrlEncodedStr: string; + LUrlEncodedBody: THttpUrlParams; +begin + LRequest := AConnection.Request as TCrossHttpRequest; + + case LRequest.GetBodyType of + btUrlEncoded: + begin + SetString(LUrlEncodedStr, + MarshaledAString((LRequest.Body as TBytesStream).Memory), + (LRequest.Body as TBytesStream).Size); + LUrlEncodedBody := THttpUrlParams.Create; + LUrlEncodedBody.Decode(LUrlEncodedStr); + FreeAndNil(LRequest.FBody); + LRequest.FBody := LUrlEncodedBody; + end; + + btBinary: + begin + (LRequest.Body as TStream).Position := 0; + end; + end; + + if Assigned(FOnPostDataEnd) then + FOnPostDataEnd(Self, AConnection); +end; + +function TCrossHttpServer.LockMiddlewares: TCrossHttpRouters; +begin + Result := FMiddlewares; + FMiddlewaresLock.BeginWrite; +end; + +function TCrossHttpServer.LockRouters: TCrossHttpRouters; +begin + Result := FRouters; + FRoutersLock.BeginWrite; +end; + +procedure TCrossHttpServer.LogicReceived(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +begin + ParseRecvData(AConnection as ICrossHttpConnection, ABuf, ALen); +end; + +function TCrossHttpServer.Use(const AMethod, APath: string; + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := RegisterMiddleware(AMethod, APath, nil, AMiddlewareMethod, nil, nil); +end; + +function TCrossHttpServer.Use(const AMethod, APath: string; + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := RegisterMiddleware(AMethod, APath, AMiddlewareProc, nil, nil, nil); +end; + +function TCrossHttpServer.Use(const APath: string; + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Use('*', APath, AMiddlewareMethod); +end; + +function TCrossHttpServer.Use(const APath: string; + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Use('*', APath, AMiddlewareProc); +end; + +function TCrossHttpServer.Use( + AMiddlewareMethod: TCrossHttpRouterMethod): ICrossHttpServer; +begin + Result := Use('*', '*', AMiddlewareMethod); +end; + +procedure TCrossHttpServer.UnlockMiddlewares; +begin + FMiddlewaresLock.EndWrite; +end; + +procedure TCrossHttpServer.UnlockRouters; +begin + FRoutersLock.EndWrite; +end; + +function TCrossHttpServer.Use( + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Use('*', '*', AMiddlewareMethod2); +end; + +function TCrossHttpServer.Use( + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Use('*', '*', AMiddlewareProc2); +end; + +function TCrossHttpServer.Use(const AMethod, APath: string; + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := RegisterMiddleware(AMethod, APath, nil, nil, nil, AMiddlewareMethod2); +end; + +function TCrossHttpServer.Use(const AMethod, APath: string; + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := RegisterMiddleware(AMethod, APath, nil, nil, AMiddlewareProc2, nil); +end; + +function TCrossHttpServer.Use(const APath: string; + AMiddlewareMethod2: TCrossHttpRouterMethod2): ICrossHttpServer; +begin + Result := Use('*', APath, AMiddlewareMethod2); +end; + +function TCrossHttpServer.Use(const APath: string; + AMiddlewareProc2: TCrossHttpRouterProc2): ICrossHttpServer; +begin + Result := Use('*', APath, AMiddlewareProc2); +end; + +function TCrossHttpServer.Use( + AMiddlewareProc: TCrossHttpRouterProc): ICrossHttpServer; +begin + Result := Use('*', '*', AMiddlewareProc); +end; + +{ TCrossHttpRequest } + +constructor TCrossHttpRequest.Create(AConnection: ICrossHttpConnection); +begin + FConnection := AConnection; + + FRawRequest := TBytesStream.Create(nil); + FHeader := THttpHeader.Create; + FCookies := TRequestCookies.Create; + FParams := THttpUrlParams.Create; + FQuery := THttpUrlParams.Create; + + Reset; +end; + +destructor TCrossHttpRequest.Destroy; +begin + FreeAndNil(FRawRequest); + FreeAndNil(FHeader); + FreeAndNil(FCookies); + FreeAndNil(FParams); + FreeAndNil(FQuery); + FreeAndNil(FBody); + inherited; +end; + +function TCrossHttpRequest.GetAccept: string; +begin + Result := FAccept; +end; + +function TCrossHttpRequest.GetAcceptEncoding: string; +begin + Result := FAcceptEncoding; +end; + +function TCrossHttpRequest.GetAcceptLanguage: string; +begin + Result := FAcceptLanguage; +end; + +function TCrossHttpRequest.GetAuthorization: string; +begin + Result := FAuthorization; +end; + +function TCrossHttpRequest.GetBody: TObject; +begin + Result := FBody; +end; + +function TCrossHttpRequest.GetBodyType: TBodyType; +begin + Result := FBodyType; +end; + +function TCrossHttpRequest.GetConnection: ICrossHttpConnection; +begin + Result := FConnection; +end; + +function TCrossHttpRequest.GetContentEncoding: string; +begin + Result := FContentEncoding; +end; + +function TCrossHttpRequest.GetContentLength: Int64; +begin + Result := FContentLength; +end; + +function TCrossHttpRequest.GetContentType: string; +begin + Result := FContentType; +end; + +function TCrossHttpRequest.GetCookies: TRequestCookies; +begin + Result := FCookies; +end; + +function TCrossHttpRequest.GetHeader: THttpHeader; +begin + Result := FHeader; +end; + +function TCrossHttpRequest.GetHostName: string; +begin + Result := FHostName; +end; + +function TCrossHttpRequest.GetHostPort: Word; +begin + Result := FHostPort; +end; + +function TCrossHttpRequest.GetIfModifiedSince: TDateTime; +begin + Result := FIfModifiedSince; +end; + +function TCrossHttpRequest.GetIfNoneMatch: string; +begin + Result := FIfNoneMatch; +end; + +function TCrossHttpRequest.GetIfRange: string; +begin + Result := FIfRange; +end; + +function TCrossHttpRequest.GetIsChunked: Boolean; +begin + Result := SameText(FTransferEncoding, 'chunked'); +end; + +function TCrossHttpRequest.GetIsMultiPartFormData: Boolean; +begin + Result := SameText(FContentType, 'multipart/form-data'); +end; + +function TCrossHttpRequest.GetIsUrlEncodedFormData: Boolean; +begin + Result := SameText(FContentType, 'application/x-www-form-urlencoded'); +end; + +function TCrossHttpRequest.GetKeepAlive: Boolean; +begin + Result := FKeepAlive; +end; + +function TCrossHttpRequest.GetMethod: string; +begin + Result := FMethod; +end; + +function TCrossHttpRequest.GetParams: THttpUrlParams; +begin + Result := FParams; +end; + +function TCrossHttpRequest.GetPath: string; +begin + Result := FPath; +end; + +function TCrossHttpRequest.GetPostDataSize: Int64; +begin + Result := FPostDataSize; +end; + +function TCrossHttpRequest.GetQuery: THttpUrlParams; +begin + Result := FQuery; +end; + +function TCrossHttpRequest.GetRange: string; +begin + Result := FRange; +end; + +function TCrossHttpRequest.GetRawPathAndParams: string; +begin + Result := FRawPathAndParams; +end; + +function TCrossHttpRequest.GetRawRequestText: string; +begin + Result := FRawRequestText; +end; + +function TCrossHttpRequest.GetReferer: string; +begin + Result := FReferer; +end; + +function TCrossHttpRequest.GetRequestBoundary: string; +begin + Result := FRequestBoundary; +end; + +function TCrossHttpRequest.GetRequestCmdLine: string; +begin + Result := FRequestCmdLine; +end; + +function TCrossHttpRequest.GetRequestConnection: string; +begin + Result := FRequestConnection; +end; + +function TCrossHttpRequest.GetSession: ISession; +begin + Result := FSession; +end; + +function TCrossHttpRequest.GetTransferEncoding: string; +begin + Result := FTransferEncoding; +end; + +function TCrossHttpRequest.GetUserAgent: string; +begin + Result := FUserAgent; +end; + +function TCrossHttpRequest.GetVersion: string; +begin + Result := FVersion; +end; + +function TCrossHttpRequest.GetXForwardedFor: string; +begin + Result := FXForwardedFor; +end; + +function TCrossHttpRequest.ParseRequestData: Boolean; +var + LRequestHeader: string; + I, J: Integer; +begin + SetString(FRawRequestText, MarshaledAString(FRawRequest.Memory), FRawRequest.Size); + I := FRawRequestText.IndexOf(#13#10); + // 第一行是请求命令行 + // GET /home?param=123 HTTP/1.1 + FRequestCmdLine := FRawRequestText.Substring(0, I); + // 第二行起是请求头 + LRequestHeader := FRawRequestText.Substring(I + 2); + // 解析请求头 + FHeader.Decode(LRequestHeader); + + // 请求方法(GET, POST, PUT, DELETE...) + I := FRequestCmdLine.IndexOf(' '); + FMethod := FRequestCmdLine.Substring(0, I).ToUpper; + + // 路径及参数(/home?param=123) + J := FRequestCmdLine.IndexOf(' ', I + 1); + FRawPathAndParams := FRequestCmdLine.Substring(I + 1, J - I - 1); + + // 请求的HTTP版本(HTTP/1.1) + FVersion := FRequestCmdLine.Substring(J + 1).ToUpper; + + // 解析?key1=value1&key2=value2参数 + J := FRawPathAndParams.IndexOf('?'); + if (J < 0) then + begin + FRawPath := FRawPathAndParams; + FRawParamsText := ''; + end else + begin + FRawPath := FRawPathAndParams.Substring(0, J); + FRawParamsText := FRawPathAndParams.Substring(J + 1); + end; + FPath := TNetEncoding.URL.Decode(FRawPath); + FQuery.Decode(FRawParamsText); + + // HTTP协议版本 + if (FVersion = '') then + FVersion := 'HTTP/1.0'; + if (FVersion = 'HTTP/1.0') then + FHttpVerNum := 10 + else + FHttpVerNum := 11; + FKeepAlive := (FHttpVerNum = 11); + + // 解析Cookies + FCookies.Decode(FHeader['Cookie'], True); + + FContentType := FHeader['Content-Type']; + FRequestBoundary := ''; + J := FContentType.IndexOf(';'); + if (J >= 0) then + begin + FRequestBoundary := FContentType.Substring(J + 1); + if FRequestBoundary.StartsWith(' boundary=', True) then + FRequestBoundary := FRequestBoundary.Substring(10); + + FContentType := FContentType.Substring(0, J); + end; + + FContentLength := StrToInt64Def(FHeader['Content-Length'], -1); + + FRequestHost := FHeader['Host']; + J := FRequestHost.IndexOf(':'); + if (J >= 0) then + begin + FHostName := FRequestHost.Substring(0, J); + FHostPort := FRequestHost.Substring(J + 1).ToInteger; + end else + begin + FHostName := FRequestHost; + FHostPort := TCrossHttpServer(FConnection.Owner).Port; + end; + + FRequestConnection := FHeader['Connection']; + // HTTP/1.0 默认KeepAlive=False,只有显示指定了Connection: keep-alive才认为KeepAlive=True + // HTTP/1.1 默认KeepAlive=True,只有显示指定了Connection: close才认为KeepAlive=False + if FHttpVerNum = 10 then + FKeepAlive := SameText(FRequestConnection, 'keep-alive') + else if SameText(FRequestConnection, 'close') then + FKeepAlive := False; + + FTransferEncoding:= FHeader['Transfer-Encoding']; + FContentEncoding:= FHeader['Content-Encoding']; + FAccept:= FHeader['Accept']; + FReferer:= FHeader['Referer']; + FAcceptLanguage:= FHeader['Accept-Language']; + FAcceptEncoding:= FHeader['Accept-Encoding']; + FUserAgent:= FHeader['User-Agent']; + FAuthorization:= FHeader['Authorization']; + FRequestCookies:= FHeader['Cookie']; + FIfModifiedSince := TCrossHttpUtils.RFC1123_StrToDate(FHeader['If-Modified-Since']); + FIfNoneMatch := FHeader['If-None-Match']; + FRange := FHeader['Range']; + FIfRange := FHeader['If-Range']; + FXForwardedFor:= FHeader['X-Forwarded-For']; + + if IsMultiPartFormData then + FBodyType := btMultiPart + else if IsUrlEncodedFormData then + FBodyType := btUrlEncoded + else + FBodyType := btBinary; + + Result := True; +end; + +procedure TCrossHttpRequest.Reset; +begin + FRawRequest.Clear; + + FParseState := psHeader; + CR := 0; + LF := 0; + FPostDataSize := 0; + FreeAndNil(FBody); +end; + +{ TCrossHttpResponse } + +constructor TCrossHttpResponse.Create(AConnection: ICrossHttpConnection); +begin + FConnection := AConnection; + FHeader := THttpHeader.Create; + FCookies := TResponseCookies.Create; + FStatusCode := 200; +end; + +destructor TCrossHttpResponse.Destroy; +begin + FreeAndNil(FHeader); + FreeAndNil(FCookies); + inherited; +end; + +procedure TCrossHttpResponse.Download(const AFileName: string; + ACallback: TProc); +begin + Attachment(AFileName); + SendFile(AFileName, ACallback); +end; + +function TCrossHttpResponse.GetConnection: ICrossHttpConnection; +begin + Result := FConnection; +end; + +function TCrossHttpResponse.GetContentType: string; +begin + Result := FHeader['Content-Type']; +end; + +function TCrossHttpResponse.GetCookies: TResponseCookies; +begin + Result := FCookies; +end; + +function TCrossHttpResponse.GetHeader: THttpHeader; +begin + Result := FHeader; +end; + +function TCrossHttpResponse.GetLocation: string; +begin + Result := FHeader['Location']; +end; + +function TCrossHttpResponse.GetRequest: ICrossHttpRequest; +begin + Result := FConnection.Request; +end; + +function TCrossHttpResponse.GetSent: Boolean; +begin + Result := (AtomicCmpExchange(FSendStatus, 0, 0) > 0); +end; + +function TCrossHttpResponse.GetStatusCode: Integer; +begin + Result := FStatusCode; +end; + +procedure TCrossHttpResponse.Json(const AJson: string; + ACallback: TProc); +begin + SetContentType(TMediaType.APPLICATION_JSON_UTF8); + Send(AJson, ACallback); +end; + +procedure TCrossHttpResponse.Redirect(const AUrl: string; ACallback: TProc); +begin + SetLocation(AUrl); + SendStatus(302, '', ACallback); +end; + +procedure TCrossHttpResponse.Reset; +begin + FStatusCode := 200; + FHeader.Clear; + FCookies.Clear; + FSendStatus := 0; +end; + +procedure TCrossHttpResponse.Attachment(const AFileName: string); +begin + if (GetContentType = '') then + SetContentType(TCrossHttpUtils.GetFileMIMEType(AFileName)); + FHeader['Content-Disposition'] := 'attachment; filename="' + + TNetEncoding.URL.Encode(TPath.GetFileName(AFileName)) + '"'; +end; + +procedure TCrossHttpResponse.Send(const ABody; ACount: NativeInt; + ACallback: TProc); +var + LCompressType: TCompressType; +begin + if _CheckCompress(ACount, LCompressType) then + SendZCompress(ABody, ACount, LCompressType, ACallback) + else + SendNoCompress(ABody, ACount, ACallback); +end; + +procedure TCrossHttpResponse.Send(const ABody: TBytes; AOffset, ACount: NativeInt; + ACallback: TProc); +var + LBody: TBytes; + LOffset, LCount: NativeInt; +begin + // 增加其引用计数 + LBody := ABody; + + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(Length(ABody), LOffset, LCount); + + Send(LBody[LOffset], LCount, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + // 减少引用计数 + LBody := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.Send(const ABody: TBytes; + ACallback: TProc); +begin + Send(ABody, 0, Length(ABody), ACallback); +end; + +procedure TCrossHttpResponse.Send(const ABody: TStream; const AOffset, + ACount: Int64; ACallback: TProc); +var + LCompressType: TCompressType; +begin + if _CheckCompress(ABody.Size, LCompressType) then + SendZCompress(ABody, AOffset, ACount, LCompressType, ACallback) + else + SendNoCompress(ABody, AOffset, ACount, ACallback); +end; + +procedure TCrossHttpResponse.Send(const ABody: TStream; ACallback: TProc); +begin + Send(ABody, 0, 0, ACallback); +end; + +procedure TCrossHttpResponse.Send(const ABody: string; + ACallback: TProc); +var + LBody: TBytes; +begin + LBody := TEncoding.UTF8.GetBytes(ABody); + if (GetContentType = '') then + SetContentType(TMediaType.TEXT_HTML_UTF8); + + Send(LBody, ACallback); +end; + +procedure TCrossHttpResponse.SendNoCompress( + const AChunkSource: TFunc; + ACallback: TProc); +{ +HTTP头\r\n\r\n +块尺寸\r\n +块内容 +\r\n块尺寸\r\n +块内容 +\r\n0\r\n\r\n +} +type + TChunkState = (csHead, csBody, csDone); +const + CHUNK_END: array [0..6] of Byte = (13, 10, 48, 13, 10, 13, 10); // \r\n0\r\n\r\n +var + LHeaderBytes, LChunkHeader: TBytes; + LIsFirstChunk: Boolean; + LChunkState: TChunkState; + LChunkData: Pointer; + LChunkSize: NativeInt; +begin + LIsFirstChunk := True; + LChunkState := csHead; + + _Send( + // HEADER + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + LHeaderBytes := _CreateHeader(0, True); + + AData^ := @LHeaderBytes[0]; + ACount^ := Length(LHeaderBytes); + + Result := (ACount^ > 0); + end, + // BODY + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + case LChunkState of + csHead: + begin + LChunkData := nil; + LChunkSize := 0; + if not Assigned(AChunkSource) + or not AChunkSource(@LChunkData, @LChunkSize) + or (LChunkData = nil) + or (LChunkSize <= 0) then + begin + LChunkState := csDone; + + AData^ := @CHUNK_END[0]; + ACount^ := Length(CHUNK_END); + + Result := (ACount^ > 0); + + Exit; + end; + + LChunkHeader := TEncoding.ANSI.GetBytes(IntToHex(LChunkSize, 0)) + [13, 10]; + if LIsFirstChunk then + LIsFirstChunk := False + else + LChunkHeader := [13, 10] + LChunkHeader; + + LChunkState := csBody; + + AData^ := @LChunkHeader[0]; + ACount^ := Length(LChunkHeader); + + Result := (ACount^ > 0); + end; + + csBody: + begin + LChunkState := csHead; + + AData^ := LChunkData; + ACount^ := LChunkSize; + + Result := (ACount^ > 0); + end; + + csDone: + begin + Result := False; + end; + else + Result := False; + end; + end, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + LHeaderBytes := nil; + LChunkHeader := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendFile(const AFileName: string; + ACallback: TProc); +var + LStream: TFileStream; + LLastModified: TDateTime; + LRequest: TCrossHttpRequest; + LLastModifiedStr, LETag: string; + LRangeStr: string; + LRangeStrArr: TArray; + LRangeBegin, LRangeEnd, LOffset, LCount: Int64; +begin + if not TFile.Exists(AFileName) then + begin + FHeader.Remove('Content-Disposition'); + SendStatus(404, ACallback); + Exit; + end; + + if (GetContentType = '') then + SetContentType(TCrossHttpUtils.GetFileMIMEType(AFileName)); + + try + // 根据请求头中的时间戳决定是否需要发送文件数据 + // 当请求头中的时间戳与文件时间一致时, 浏览器会自动从本地加载文件数据 + // 服务端无需发送文件数据 + LRequest := GetRequest as TCrossHttpRequest; + LLastModified := TFile.GetLastWriteTime(AFileName); + + if (LRequest.IfModifiedSince > 0) and (LRequest.IfModifiedSince >= (LLastModified - (1 / SecsPerDay))) then + begin + // 304不要带任何body数据, 否则部分浏览器会报告无效的RESPONSE + SendStatus(304, '', ACallback); + Exit; + end; + + LLastModifiedStr := TCrossHttpUtils.RFC1123_DateToStr(LLastModified); + + LETag := '"' + TUtils.BytesToHex(THashMD5.GetHashBytes(AFileName + LLastModifiedStr)) + '"'; + if (LRequest.IfNoneMatch = LETag) then + begin + // 304不要带任何body数据, 否则部分浏览器会报告无效的RESPONSE + SendStatus(304, '', ACallback); + Exit; + end; + + LStream := TFileStream.Create(AFileName, fmOpenRead, fmShareDenyNone); + except + on e: Exception do + begin + FHeader.Remove('Content-Disposition'); + SendStatus(404, Format('%s, %s', [e.ClassName, e.Message]), ACallback); + Exit; + end; + end; + + // 在响应头中加入文件时间戳 + // 浏览器会根据该时间戳决定是否从本地缓存中直接加载数据 + FHeader['Last-Modified'] := LLastModifiedStr; + FHeader['ETag'] := LETag; + + // 告诉浏览器支持分块传输 + FHeader['Accept-Ranges'] := 'bytes'; + + // 收到分块取数据头 + // Range: bytes=[x]-[y] + LRangeStr := LRequest.Range; + if (LRangeStr <> '') + and ((LRequest.IfRange = '') or (LRequest.IfRange = LETag)) then + begin + LRangeStr := LRangeStr.Substring(LRangeStr.IndexOf('=') + 1); + LRangeStrArr := LRangeStr.Split(['-']); + if (Length(LRangeStrArr) >= 2) then + begin + LRangeBegin := StrToInt64Def(LRangeStrArr[0], 0); + LRangeEnd := StrToInt64Def(LRangeStrArr[1], 0); + end else + if (Length(LRangeStrArr) >= 1) then + begin + LRangeBegin := StrToInt64Def(LRangeStrArr[0], 0); + LRangeEnd := LStream.Size - 1; + end else + begin + LRangeBegin := 0; + LRangeEnd := LStream.Size - 1; + end; + + LOffset := LRangeBegin; + LCount := LRangeEnd - LRangeBegin + 1; + + // 返回分块信息 + // Content-Range: bytes [x]-[y]/file-size + FHeader['Content-Range'] := Format('bytes %d-%d/%d', + [LRangeBegin, LRangeEnd, LStream.Size]); + + // 断点续传需要返回206状态码, 而不是200 + FStatusCode := 206; + end else + begin + LOffset := 0; + LCount := LStream.Size; + end; + + Send(LStream, LOffset, LCount, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + FreeAndNil(LStream); + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SetContentType(const Value: string); +begin + FHeader['Content-Type'] := Value; +end; + +procedure TCrossHttpResponse.SetLocation(const Value: string); +begin + FHeader['Location'] := Value; +end; + +procedure TCrossHttpResponse.SetStatusCode(Value: Integer); +begin + FStatusCode := Value; +end; + +procedure TCrossHttpResponse._AdjustOffsetCount(const ABodySize: NativeInt; + var AOffset, ACount: NativeInt); +begin + {$region '修正 AOffset'} + // 偏移为正数, 从头部开始计算偏移 + if (AOffset >= 0) then + begin + AOffset := AOffset; + if (AOffset >= ABodySize) then + AOffset := ABodySize - 1; + end else + // 偏移为负数, 从尾部开始计算偏移 + begin + AOffset := ABodySize + AOffset; + if (AOffset < 0) then + AOffset := 0; + end; + {$endregion} + + {$region '修正 ACount'} + // ACount<=0表示需要处理所有数据 + if (ACount <= 0) then + ACount := ABodySize; + + if (ABodySize - AOffset < ACount) then + ACount := ABodySize - AOffset; + {$endregion} +end; + +procedure TCrossHttpResponse._AdjustOffsetCount(const ABodySize: Int64; + var AOffset, ACount: Int64); +begin + {$region '修正 AOffset'} + // 偏移为正数, 从头部开始计算偏移 + if (AOffset >= 0) then + begin + AOffset := AOffset; + if (AOffset >= ABodySize) then + AOffset := ABodySize - 1; + end else + // 偏移为负数, 从尾部开始计算偏移 + begin + AOffset := ABodySize + AOffset; + if (AOffset < 0) then + AOffset := 0; + end; + {$endregion} + + {$region '修正 ACount'} + // ACount<=0表示需要处理所有数据 + if (ACount <= 0) then + ACount := ABodySize; + + if (ABodySize - AOffset < ACount) then + ACount := ABodySize - AOffset; + {$endregion} +end; + +function TCrossHttpResponse._CheckCompress(const ABodySize: Int64; + var ACompressType: TCompressType): Boolean; +var + LContType, LRequestAcceptEncoding: string; + LServer: ICrossHttpServer; +begin + LContType := GetContentType; + LServer := FConnection.Server; + + if LServer.Compressible + and (ABodySize > 0) + and ((LServer.MinCompressSize <= 0) or (ABodySize >= LServer.MinCompressSize)) + and ((Pos('text/', LContType) > 0) + or (Pos('application/json', LContType) > 0) + or (Pos('javascript', LContType) > 0) + or (Pos('xml', LContType) > 0) + ) then + begin + LRequestAcceptEncoding := GetRequest.AcceptEncoding; + + if (Pos('gzip', LRequestAcceptEncoding) > 0) then + begin + ACompressType := ctGZip; + Exit(True); + end else + if (Pos('deflate', LRequestAcceptEncoding) > 0) then + begin + ACompressType := ctDeflate; + Exit(True); + end; + end; + + Result := False; +end; + +function TCrossHttpResponse._CreateHeader(const ABodySize: Int64; + AChunked: Boolean): TBytes; +var + LHeaderStr: string; + LCookie: TResponseCookie; +begin + if (GetContentType = '') then + SetContentType(TMediaType.APPLICATION_OCTET_STREAM); + if (FHeader['Connection'] = '') then + begin + if FConnection.Request.KeepAlive then + FHeader['Connection'] := 'keep-alive' + else + FHeader['Connection'] := 'close'; + end; + + if AChunked then + FHeader['Transfer-Encoding'] := 'chunked' + else + FHeader['Content-Length'] := ABodySize.ToString; + + if (FHeader['Server'] = '') then + FHeader['Server'] := CROSS_HTTP_SERVER_NAME; + + LHeaderStr := FConnection.Request.Version + ' ' + FStatusCode.ToString + ' ' + + TCrossHttpUtils.GetHttpStatusText(FStatusCode) + #13#10; + + for LCookie in FCookies do + LHeaderStr := LHeaderStr + 'Set-Cookie: ' + LCookie.Encode + #13#10; + + LHeaderStr := LHeaderStr + FHeader.Encode; + + Result := TEncoding.ANSI.GetBytes(LHeaderStr); +end; + +procedure TCrossHttpResponse._Send(ASource: TFunc; + ACallback: TProc); +var + LSender: TProc; + LKeepAlive: Boolean; + LStatusCode: Integer; +begin + AtomicIncrement(FSendStatus); + + LKeepAlive := FConnection.Request.KeepAlive; + LStatusCode := FStatusCode; + + LSender := + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + var + LData: Pointer; + LCount: NativeInt; + begin + if not ASuccess then + begin + if Assigned(ACallback) then + ACallback(AConnection, False); + + AConnection.Close; + + LSender := nil; + + Exit; + end; + + LData := nil; + LCount := 0; + if not Assigned(ASource) + or not ASource(@LData, @LCount) + or (LData = nil) + or (LCount <= 0) then + begin + if Assigned(ACallback) then + ACallback(AConnection, True); + + if not LKeepAlive + or (LStatusCode >= 400{如果发送的是出错状态码, 则发送完成之后断开连接}) then + AConnection.Disconnect; + + LSender := nil; + + Exit; + end; + + AConnection.SendBuf(LData^, LCount, LSender); + end; + + LSender(FConnection, True); +end; + +procedure TCrossHttpResponse._Send(AHeaderSource, + ABodySource: TFunc; + ACallback: TProc); +var + LHeaderDone: Boolean; +begin + LHeaderDone := False; + + _Send( + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + if not LHeaderDone then + begin + LHeaderDone := True; + Result := Assigned(AHeaderSource) and AHeaderSource(AData, ACount); + end else + begin + Result := Assigned(ABodySource) and ABodySource(AData, ACount); + end; + end, + ACallback); +end; + +procedure TCrossHttpResponse.SendNoCompress(const ABody; ACount: NativeInt; + ACallback: TProc); +var + P: PByte; + LSize: NativeInt; + LHeaderBytes: TBytes; +begin + P := @ABody; + LSize := ACount; + + _Send( + // HEADER + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + LHeaderBytes := _CreateHeader(LSize, False); + + AData^ := @LHeaderBytes[0]; + ACount^ := Length(LHeaderBytes); + + Result := (ACount^ > 0); + end, + // BODY + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + AData^ := P; + ACount^ := Min(LSize, SND_BUF_SIZE); + Result := (ACount^ > 0); + + if (LSize > SND_BUF_SIZE) then + begin + Inc(P, SND_BUF_SIZE); + Dec(LSize, SND_BUF_SIZE); + end else + begin + LSize := 0; + P := nil; + end; + end, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + LHeaderBytes := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendNoCompress(const ABody: TBytes; AOffset, + ACount: NativeInt; ACallback: TProc); +var + LBody: TBytes; + LOffset, LCount: NativeInt; +begin + // 增加其引用计数 + LBody := ABody; + + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(Length(ABody), LOffset, LCount); + + SendNoCompress(LBody[LOffset], LCount, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + // 减少引用计数 + LBody := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendNoCompress(const ABody: TBytes; + ACallback: TProc); +begin + SendNoCompress(ABody, 0, Length(ABody), ACallback); +end; + +procedure TCrossHttpResponse.SendNoCompress(const ABody: TStream; const AOffset, + ACount: Int64; ACallback: TProc); +var + LOffset, LCount: Int64; + LBody: TStream; + LHeaderBytes, LBuffer: TBytes; +begin + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(ABody.Size, LOffset, LCount); + + if (ABody is TCustomMemoryStream) then + begin + SendNoCompress(Pointer(IntPtr(TCustomMemoryStream(ABody).Memory) + LOffset)^, LCount, ACallback); + Exit; + end; + + LBody := ABody; + LBody.Position := LOffset; + + SetLength(LBuffer, SND_BUF_SIZE); + + _Send( + // HEADER + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + LHeaderBytes := _CreateHeader(LCount, False); + + AData^ := @LHeaderBytes[0]; + ACount^ := Length(LHeaderBytes); + + Result := (ACount^ > 0); + end, + // BODY + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + if (LCount <= 0) then Exit(False); + + AData^ := @LBuffer[0]; + ACount^ := LBody.Read(LBuffer[0], Min(LCount, SND_BUF_SIZE)); + + Result := (ACount^ > 0); + + if Result then + Dec(LCount, ACount^); + end, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + LHeaderBytes := nil; + LBuffer := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendNoCompress(const ABody: TStream; + ACallback: TProc); +begin + SendNoCompress(ABody, 0, 0, ACallback); +end; + +procedure TCrossHttpResponse.SendNoCompress(const ABody: string; + ACallback: TProc); +var + LBody: TBytes; +begin + LBody := TEncoding.UTF8.GetBytes(ABody); + if (GetContentType = '') then + SetContentType(TMediaType.TEXT_HTML_UTF8); + + SendNoCompress(LBody, ACallback); +end; + +procedure TCrossHttpResponse.SendStatus(AStatusCode: Integer; + const ADescription: string; ACallback: TProc); +begin + FStatusCode := AStatusCode; + Send(ADescription, ACallback); +end; + +procedure TCrossHttpResponse.SendStatus(AStatusCode: Integer; + ACallback: TProc); +begin + SendStatus(AStatusCode, TCrossHttpUtils.GetHttpStatusText(AStatusCode), ACallback); +end; + +procedure TCrossHttpResponse.SendZCompress( + const AChunkSource: TFunc; + ACompressType: TCompressType; ACallback: TProc); +{ + 本方法实现了一边压缩一边发送数据, 所以可以支持无限大的分块数据的压缩发送, + 而不用占用太多的内存和CPU + + zlib参考手册: http://www.zlib.net/zlib_how.html +} +const + WINDOW_BITS: array [TCompressType] of Integer = (15 + 16{gzip}, 15{deflate}); + CONTENT_ENCODING: array [TCompressType] of string = ('gzip', 'deflate'); +var + LZStream: TZStreamRec; + LZFlush: Integer; + LZResult: Integer; + LOutSize: Integer; + LBuffer: TBytes; +begin + // 返回压缩方式 + FHeader['Content-Encoding'] := CONTENT_ENCODING[ACompressType]; + + // 明确告知缓存服务器按照 Accept-Encoding 字段的内容, 分别缓存不同的版本 + FHeader['Vary'] := 'Accept-Encoding'; + + SetLength(LBuffer, SND_BUF_SIZE); + + FillChar(LZStream, SizeOf(TZStreamRec), 0); + LZResult := Z_OK; + LZFlush := Z_NO_FLUSH; + + if (deflateInit2(LZStream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, WINDOW_BITS[ACompressType], 8, Z_DEFAULT_STRATEGY) <> Z_OK) then + begin + if Assigned(ACallback) then + ACallback(FConnection, False); + Exit; + end; + + SendNoCompress( + // CHUNK + function(AData: PPointer; ACount: PNativeInt): Boolean + var + LChunkData: Pointer; + LChunkSize: NativeInt; + begin + repeat + // 当 deflate(LZStream, Z_FINISH) 被调用后 + // 返回 Z_STREAM_END 表示所有数据处理完毕 + if (LZResult = Z_STREAM_END) then + begin + AData^ := nil; + ACount^ := 0; + Exit(False); + end; + + // 输入缓冲区已经处理完毕 + // 需要填入新数据 + if (LZStream.avail_in = 0) then + begin + LChunkData := nil; + LChunkSize := 0; + if not Assigned(AChunkSource) + or not AChunkSource(@LChunkData, @LChunkSize) + or (LChunkData = nil) + or (LChunkSize <= 0) then + LZFlush := Z_FINISH // 如果没有后续数据了, 准备结束压缩 + else + LZFlush := Z_NO_FLUSH; + + // 压缩数据输入缓冲区 + LZStream.avail_in := LChunkSize; + LZStream.next_in := LChunkData; + end; + + // 压缩数据输出缓冲区 + LZStream.avail_out := SND_BUF_SIZE; + LZStream.next_out := @LBuffer[0]; + + // 进行压缩处理 + // 输入缓冲区数据可以大于输出缓冲区 + // 这种情况可以多次调用 deflate 分批压缩, + // 直到 avail_in=0 表示当前输入缓冲区数据已压缩完毕 + LZResult := deflate(LZStream, LZFlush); + + // 压缩出错之后直接结束 + // 这里也可能会返回 Z_STREAM_END(1) + // 返回 Z_STREAM_END(1) 这一次还是有数据的 + // 所以要到下次 CHUNK 函数被调用的时候再结束 + if (LZResult < 0) then + begin + AData^ := nil; + ACount^ := 0; + Exit(False); + end; + + // 已压缩完成的数据大小 + LOutSize := SND_BUF_SIZE - LZStream.avail_out; + until (LOutSize > 0); + + // 已压缩的数据 + AData^ := @LBuffer[0]; + ACount^ := LOutSize; + + Result := (ACount^ > 0); + end, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + LBuffer := nil; + deflateEnd(LZStream); + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendZCompress(const ABody; ACount: NativeInt; + ACompressType: TCompressType; ACallback: TProc); +var + P: PByte; + LSize: NativeInt; +begin + P := @ABody; + LSize := ACount; + + SendZCompress( + // CHUNK + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + AData^ := P; + ACount^ := Min(LSize, SND_BUF_SIZE); + Result := (ACount^ > 0); + + if (LSize > SND_BUF_SIZE) then + begin + Inc(P, SND_BUF_SIZE); + Dec(LSize, SND_BUF_SIZE); + end else + begin + LSize := 0; + P := nil; + end; + end, + ACompressType, + ACallback); +end; + +procedure TCrossHttpResponse.SendZCompress(const ABody: TBytes; AOffset, + ACount: NativeInt; ACompressType: TCompressType; + ACallback: TProc); +var + LBody: TBytes; + LOffset, LCount: NativeInt; +begin + // 增加其引用计数 + LBody := ABody; + + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(Length(ABody), LOffset, LCount); + + SendZCompress(LBody[LOffset], LCount, ACompressType, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + // 减少引用计数 + LBody := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendZCompress(const ABody: TBytes; + ACompressType: TCompressType; ACallback: TProc); +begin + SendZCompress(ABody, 0, Length(ABody), ACompressType, ACallback); +end; + +procedure TCrossHttpResponse.SendZCompress(const ABody: TStream; const AOffset, + ACount: Int64; ACompressType: TCompressType; + ACallback: TProc); +var + LOffset, LCount: Int64; + LBody: TStream; + LBuffer: TBytes; +begin + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(ABody.Size, LOffset, LCount); + + if (ABody is TCustomMemoryStream) then + begin + SendZCompress(Pointer(IntPtr(TCustomMemoryStream(ABody).Memory) + LOffset)^, LCount, ACompressType, ACallback); + Exit; + end; + + LBody := ABody; + LBody.Position := LOffset; + + SetLength(LBuffer, SND_BUF_SIZE); + + SendZCompress( + // CHUNK + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + if (LCount <= 0) then Exit(False); + + ACount^ := LBody.Read(LBuffer, Min(LCount, SND_BUF_SIZE)); + AData^ := @LBuffer[0]; + + Result := (ACount^ > 0); + + if Result then + Dec(LCount, ACount^); + end, + ACompressType, + // CALLBACK + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + LBuffer := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossHttpResponse.SendZCompress(const ABody: TStream; + ACompressType: TCompressType; ACallback: TProc); +begin + SendZCompress(ABody, 0, 0, ACompressType, ACallback); +end; + +procedure TCrossHttpResponse.SendZCompress(const ABody: string; + ACompressType: TCompressType; ACallback: TProc); +var + LBody: TBytes; +begin + LBody := TEncoding.UTF8.GetBytes(ABody); + if (GetContentType = '') then + SetContentType(TMediaType.TEXT_HTML_UTF8); + + SendZCompress(LBody, ACompressType, ACallback); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossHttpUtils.pas b/ThirdParty/DCS/Net/Net.CrossHttpUtils.pas new file mode 100644 index 00000000..470fdb97 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossHttpUtils.pas @@ -0,0 +1,1203 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossHttpUtils; + +interface + +uses + System.SysUtils; + +type + THttpStatus = record + Code: Integer; + Text: string; + end; + + TMimeValue = record + Key: string; + Value: string; + end; + +const + {$REGION 'STATUS CODE CONST'} + STATUS_CODES: array [0..56] of THttpStatus = ( + (Code: 100; Text: 'Continue'), + (Code: 101; Text: 'Switching Protocols'), + (Code: 102; Text: 'Processing'), // RFC 2518, obsoleted by RFC 4918 + (Code: 200; Text: 'OK'), + (Code: 201; Text: 'Created'), + (Code: 202; Text: 'Accepted'), + (Code: 203; Text: 'Non-Authoritative Information'), + (Code: 204; Text: 'No Content'), + (Code: 205; Text: 'Reset Content'), + (Code: 206; Text: 'Partial Content'), + (Code: 207; Text: 'Multi-Status'), // RFC 4918 + (Code: 300; Text: 'Multiple Choices'), + (Code: 301; Text: 'Moved Permanently'), + (Code: 302; Text: 'Moved Temporarily'), + (Code: 303; Text: 'See Other'), + (Code: 304; Text: 'Not Modified'), + (Code: 305; Text: 'Use Proxy'), + (Code: 307; Text: 'Temporary Redirect'), + (Code: 308; Text: 'Permanent Redirect'), // RFC 7238 + (Code: 400; Text: 'Bad Request'), + (Code: 401; Text: 'Unauthorized'), + (Code: 402; Text: 'Payment Required'), + (Code: 403; Text: 'Forbidden'), + (Code: 404; Text: 'Not Found'), + (Code: 405; Text: 'Method Not Allowed'), + (Code: 406; Text: 'Not Acceptable'), + (Code: 407; Text: 'Proxy Authentication Required'), + (Code: 408; Text: 'Request Time-out'), + (Code: 409; Text: 'Conflict'), + (Code: 410; Text: 'Gone'), + (Code: 411; Text: 'Length Required'), + (Code: 412; Text: 'Precondition Failed'), + (Code: 413; Text: 'Request Entity Too Large'), + (Code: 414; Text: 'Request-URI Too Large'), + (Code: 415; Text: 'Unsupported Media Type'), + (Code: 416; Text: 'Requested Range Not Satisfiable'), + (Code: 417; Text: 'Expectation Failed'), + (Code: 418; Text: 'I''m a teapot'), // RFC 2324 + (Code: 422; Text: 'Unprocessable Entity'), // RFC 4918 + (Code: 423; Text: 'Locked'), // RFC 4918 + (Code: 424; Text: 'Failed Dependency'), // RFC 4918 + (Code: 425; Text: 'Unordered Collection'), // RFC 4918 + (Code: 426; Text: 'Upgrade Required'), // RFC 2817 + (Code: 428; Text: 'Precondition Required'), // RFC 6585 + (Code: 429; Text: 'Too Many Requests'), // RFC 6585 + (Code: 431; Text: 'Request Header Fields Too Large'), // RFC 6585 + (Code: 500; Text: 'Internal Server Error'), + (Code: 501; Text: 'Not Implemented'), + (Code: 502; Text: 'Bad Gateway'), + (Code: 503; Text: 'Service Unavailable'), + (Code: 504; Text: 'Gateway Time-out'), + (Code: 505; Text: 'HTTP Version Not Supported'), + (Code: 506; Text: 'Variant Also Negotiates'), // RFC 2295 + (Code: 507; Text: 'Insufficient Storage'), // RFC 4918 + (Code: 509; Text: 'Bandwidth Limit Exceeded'), + (Code: 510; Text: 'Not Extended'), // RFC 2774 + (Code: 511; Text: 'Network Authentication Required') // RFC 6585 + ); + {$ENDREGION} + + {$REGION 'MIME CONST'} + MIME_TYPES: array[0..987] of TMimeValue = ( + (Key: 'ez'; Value: 'application/andrew-inset'), // do not localize + (Key: 'aw'; Value: 'application/applixware'), // do not localize + (Key: 'atom'; Value: 'application/atom+xml'), // do not localize + (Key: 'atomcat'; Value: 'application/atomcat+xml'), // do not localize + (Key: 'atomsvc'; Value: 'application/atomsvc+xml'), // do not localize + (Key: 'ccxml'; Value: 'application/ccxml+xml'), // do not localize + (Key: 'cdmia'; Value: 'application/cdmi-capability'), // do not localize + (Key: 'cdmic'; Value: 'application/cdmi-container'), // do not localize + (Key: 'cdmid'; Value: 'application/cdmi-domain'), // do not localize + (Key: 'cdmio'; Value: 'application/cdmi-object'), // do not localize + (Key: 'cdmiq'; Value: 'application/cdmi-queue'), // do not localize + (Key: 'cu'; Value: 'application/cu-seeme'), // do not localize + (Key: 'davmount'; Value: 'application/davmount+xml'), // do not localize + (Key: 'dbk'; Value: 'application/docbook+xml'), // do not localize + (Key: 'dssc'; Value: 'application/dssc+der'), // do not localize + (Key: 'xdssc'; Value: 'application/dssc+xml'), // do not localize + (Key: 'ecma'; Value: 'application/ecmascript'), // do not localize + (Key: 'emma'; Value: 'application/emma+xml'), // do not localize + (Key: 'epub'; Value: 'application/epub+zip'), // do not localize + (Key: 'exi'; Value: 'application/exi'), // do not localize + (Key: 'pfr'; Value: 'application/font-tdpfr'), // do not localize + (Key: 'gml'; Value: 'application/gml+xml'), // do not localize + (Key: 'gpx'; Value: 'application/gpx+xml'), // do not localize + (Key: 'gxf'; Value: 'application/gxf'), // do not localize + (Key: 'stk'; Value: 'application/hyperstudio'), // do not localize + (Key: 'ink'; Value: 'application/inkml+xml'), // do not localize + (Key: 'inkml'; Value: 'application/inkml+xml'), // do not localize + (Key: 'ipfix'; Value: 'application/ipfix'), // do not localize + (Key: 'jar'; Value: 'application/java-archive'), // do not localize + (Key: 'ser'; Value: 'application/java-serialized-object'), // do not localize + (Key: 'class'; Value: 'application/java-vm'), // do not localize + (Key: 'js'; Value: 'application/javascript'), // do not localize + (Key: 'json'; Value: 'application/json'), // do not localize + (Key: 'jsonml'; Value: 'application/jsonml+json'), // do not localize + (Key: 'lostxml'; Value: 'application/lost+xml'), // do not localize + (Key: 'hqx'; Value: 'application/mac-binhex40'), // do not localize + (Key: 'cpt'; Value: 'application/mac-compactpro'), // do not localize + (Key: 'mads'; Value: 'application/mads+xml'), // do not localize + (Key: 'mrc'; Value: 'application/marc'), // do not localize + (Key: 'mrcx'; Value: 'application/marcxml+xml'), // do not localize + (Key: 'ma'; Value: 'application/mathematica'), // do not localize + (Key: 'nb'; Value: 'application/mathematica'), // do not localize + (Key: 'mb'; Value: 'application/mathematica'), // do not localize + (Key: 'mathml'; Value: 'application/mathml+xml'), // do not localize + (Key: 'mbox'; Value: 'application/mbox'), // do not localize + (Key: 'mscml'; Value: 'application/mediaservercontrol+xml'), // do not localize + (Key: 'metalink'; Value: 'application/metalink+xml'), // do not localize + (Key: 'meta4'; Value: 'application/metalink4+xml'), // do not localize + (Key: 'mets'; Value: 'application/mets+xml'), // do not localize + (Key: 'mods'; Value: 'application/mods+xml'), // do not localize + (Key: 'm21'; Value: 'application/mp21'), // do not localize + (Key: 'mp21'; Value: 'application/mp21'), // do not localize + (Key: 'mp4s'; Value: 'application/mp4'), // do not localize + (Key: 'doc'; Value: 'application/msword'), // do not localize + (Key: 'dot'; Value: 'application/msword'), // do not localize + (Key: 'mxf'; Value: 'application/mxf'), // do not localize + (Key: 'bin'; Value: 'application/octet-stream'), // do not localize + (Key: 'bpk'; Value: 'application/octet-stream'), // do not localize + (Key: 'class'; Value: 'application/octet-stream'), // do not localize + (Key: 'deploy'; Value: 'application/octet-stream'), // do not localize + (Key: 'dist'; Value: 'application/octet-stream'), // do not localize + (Key: 'distz'; Value: 'application/octet-stream'), // do not localize + (Key: 'dmg'; Value: 'application/octet-stream'), // do not localize + (Key: 'dms'; Value: 'application/octet-stream'), // do not localize + (Key: 'dump'; Value: 'application/octet-stream'), // do not localize + (Key: 'elc'; Value: 'application/octet-stream'), // do not localize + (Key: 'iso'; Value: 'application/octet-stream'), // do not localize + (Key: 'lha'; Value: 'application/octet-stream'), // do not localize + (Key: 'lrf'; Value: 'application/octet-stream'), // do not localize + (Key: 'lzh'; Value: 'application/octet-stream'), // do not localize + (Key: 'mar'; Value: 'application/octet-stream'), // do not localize + (Key: 'pkg'; Value: 'application/octet-stream'), // do not localize + (Key: 'so'; Value: 'application/octet-stream'), // do not localize + (Key: 'oda'; Value: 'application/oda'), // do not localize + (Key: 'opf'; Value: 'application/oebps-package+xml'), // do not localize + (Key: 'ogx'; Value: 'application/ogg'), // do not localize + (Key: 'omdoc'; Value: 'application/omdoc+xml'), // do not localize + (Key: 'onetoc'; Value: 'application/onenote'), // do not localize + (Key: 'onetoc2'; Value: 'application/onenote'), // do not localize + (Key: 'onetmp'; Value: 'application/onenote'), // do not localize + (Key: 'onepkg'; Value: 'application/onenote'), // do not localize + (Key: 'oxps'; Value: 'application/oxps'), // do not localize + (Key: 'xer'; Value: 'application/patch-ops-error+xml'), // do not localize + (Key: 'pdf'; Value: 'application/pdf'), // do not localize + (Key: 'pgp'; Value: 'application/pgp-encrypted'), // do not localize + (Key: 'asc'; Value: 'application/pgp-signature'), // do not localize + (Key: 'sig'; Value: 'application/pgp-signature'), // do not localize + (Key: 'prf'; Value: 'application/pics-rules'), // do not localize + (Key: 'p10'; Value: 'application/pkcs10'), // do not localize + (Key: 'p7m'; Value: 'application/pkcs7-mime'), // do not localize + (Key: 'p7c'; Value: 'application/pkcs7-mime'), // do not localize + (Key: 'p7s'; Value: 'application/pkcs7-signature'), // do not localize + (Key: 'p8'; Value: 'application/pkcs8'), // do not localize + (Key: 'ac'; Value: 'application/pkix-attr-cert'), // do not localize + (Key: 'cer'; Value: 'application/pkix-cert'), // do not localize + (Key: 'crl'; Value: 'application/pkix-crl'), // do not localize + (Key: 'pkipath'; Value: 'application/pkix-pkipath'), // do not localize + (Key: 'pki'; Value: 'application/pkixcmp'), // do not localize + (Key: 'pls'; Value: 'application/pls+xml'), // do not localize + (Key: 'ai'; Value: 'application/postscript'), // do not localize + (Key: 'eps'; Value: 'application/postscript'), // do not localize + (Key: 'ps'; Value: 'application/postscript'), // do not localize + (Key: 'cww'; Value: 'application/prs.cww'), // do not localize + (Key: 'pskcxml'; Value: 'application/pskc+xml'), // do not localize + (Key: 'rdf'; Value: 'application/rdf+xml'), // do not localize + (Key: 'rif'; Value: 'application/reginfo+xml'), // do not localize + (Key: 'rnc'; Value: 'application/relax-ng-compact-syntax'), // do not localize + (Key: 'rl'; Value: 'application/resource-lists+xml'), // do not localize + (Key: 'rld'; Value: 'application/resource-lists-diff+xml'), // do not localize + (Key: 'rs'; Value: 'application/rls-services+xml'), // do not localize + (Key: 'gbr'; Value: 'application/rpki-ghostbusters'), // do not localize + (Key: 'mft'; Value: 'application/rpki-manifest'), // do not localize + (Key: 'roa'; Value: 'application/rpki-roa'), // do not localize + (Key: 'rsd'; Value: 'application/rsd+xml'), // do not localize + (Key: 'rss'; Value: 'application/rss+xml'), // do not localize + (Key: 'rtf'; Value: 'application/rtf'), // do not localize + (Key: 'sbml'; Value: 'application/sbml+xml'), // do not localize + (Key: 'scq'; Value: 'application/scvp-cv-request'), // do not localize + (Key: 'scs'; Value: 'application/scvp-cv-response'), // do not localize + (Key: 'spq'; Value: 'application/scvp-vp-request'), // do not localize + (Key: 'spp'; Value: 'application/scvp-vp-response'), // do not localize + (Key: 'sdp'; Value: 'application/sdp'), // do not localize + (Key: 'setpay'; Value: 'application/set-payment-initiation'), // do not localize + (Key: 'setreg'; Value: 'application/set-registration-initiation'), // do not localize + (Key: 'shf'; Value: 'application/shf+xml'), // do not localize + (Key: 'smi'; Value: 'application/smil+xml'), // do not localize + (Key: 'smil'; Value: 'application/smil+xml'), // do not localize + (Key: 'rq'; Value: 'application/sparql-query'), // do not localize + (Key: 'srx'; Value: 'application/sparql-results+xml'), // do not localize + (Key: 'gram'; Value: 'application/srgs'), // do not localize + (Key: 'grxml'; Value: 'application/srgs+xml'), // do not localize + (Key: 'sru'; Value: 'application/sru+xml'), // do not localize + (Key: 'ssdl'; Value: 'application/ssdl+xml'), // do not localize + (Key: 'ssml'; Value: 'application/ssml+xml'), // do not localize + (Key: 'tei'; Value: 'application/tei+xml'), // do not localize + (Key: 'teicorpus'; Value: 'application/tei+xml'), // do not localize + (Key: 'tfi'; Value: 'application/thraud+xml'), // do not localize + (Key: 'tsd'; Value: 'application/timestamped-data'), // do not localize + (Key: 'plb'; Value: 'application/vnd.3gpp.pic-bw-large'), // do not localize + (Key: 'psb'; Value: 'application/vnd.3gpp.pic-bw-small'), // do not localize + (Key: 'pvb'; Value: 'application/vnd.3gpp.pic-bw-var'), // do not localize + (Key: 'tcap'; Value: 'application/vnd.3gpp2.tcap'), // do not localize + (Key: 'pwn'; Value: 'application/vnd.3m.post-it-notes'), // do not localize + (Key: 'aso'; Value: 'application/vnd.accpac.simply.aso'), // do not localize + (Key: 'imp'; Value: 'application/vnd.accpac.simply.imp'), // do not localize + (Key: 'acu'; Value: 'application/vnd.acucobol'), // do not localize + (Key: 'atc'; Value: 'application/vnd.acucorp'), // do not localize + (Key: 'acutc'; Value: 'application/vnd.acucorp'), // do not localize + (Key: 'air'; Value: 'application/vnd.adobe.air-application-installer-package+zip'), // do not localize + (Key: 'fcdt'; Value: 'application/vnd.adobe.formscentral.fcdt'), // do not localize + (Key: 'fxp'; Value: 'application/vnd.adobe.fxp'), // do not localize + (Key: 'fxpl'; Value: 'application/vnd.adobe.fxp'), // do not localize + (Key: 'xdp'; Value: 'application/vnd.adobe.xdp+xml'), // do not localize + (Key: 'xfdf'; Value: 'application/vnd.adobe.xfdf'), // do not localize + (Key: 'ahead'; Value: 'application/vnd.ahead.space'), // do not localize + (Key: 'azf'; Value: 'application/vnd.airzip.filesecure.azf'), // do not localize + (Key: 'azs'; Value: 'application/vnd.airzip.filesecure.azs'), // do not localize + (Key: 'azw'; Value: 'application/vnd.amazon.ebook'), // do not localize + (Key: 'acc'; Value: 'application/vnd.americandynamics.acc'), // do not localize + (Key: 'ami'; Value: 'application/vnd.amiga.ami'), // do not localize + (Key: 'apk'; Value: 'application/vnd.android.package-archive'), // do not localize + (Key: 'cii'; Value: 'application/vnd.anser-web-certificate-issue-initiation'), // do not localize + (Key: 'fti'; Value: 'application/vnd.anser-web-funds-transfer-initiation'), // do not localize + (Key: 'atx'; Value: 'application/vnd.antix.game-component'), // do not localize + (Key: 'mpkg'; Value: 'application/vnd.apple.installer+xml'), // do not localize + (Key: 'm3u8'; Value: 'application/vnd.apple.mpegurl'), // do not localize + (Key: 'swi'; Value: 'application/vnd.aristanetworks.swi'), // do not localize + (Key: 'iota'; Value: 'application/vnd.astraea-software.iota'), // do not localize + (Key: 'aep'; Value: 'application/vnd.audiograph'), // do not localize + (Key: 'mpm'; Value: 'application/vnd.blueice.multipass'), // do not localize + (Key: 'bmi'; Value: 'application/vnd.bmi'), // do not localize + (Key: 'rep'; Value: 'application/vnd.businessobjects'), // do not localize + (Key: 'cdxml'; Value: 'application/vnd.chemdraw+xml'), // do not localize + (Key: 'mmd'; Value: 'application/vnd.chipnuts.karaoke-mmd'), // do not localize + (Key: 'cdy'; Value: 'application/vnd.cinderella'), // do not localize + (Key: 'cla'; Value: 'application/vnd.claymore'), // do not localize + (Key: 'rp9'; Value: 'application/vnd.cloanto.rp9'), // do not localize + (Key: 'c4g'; Value: 'application/vnd.clonk.c4group'), // do not localize + (Key: 'c4d'; Value: 'application/vnd.clonk.c4group'), // do not localize + (Key: 'c4f'; Value: 'application/vnd.clonk.c4group'), // do not localize + (Key: 'c4p'; Value: 'application/vnd.clonk.c4group'), // do not localize + (Key: 'c4u'; Value: 'application/vnd.clonk.c4group'), // do not localize + (Key: 'c11amc'; Value: 'application/vnd.cluetrust.cartomobile-config'), // do not localize + (Key: 'c11amz'; Value: 'application/vnd.cluetrust.cartomobile-config-pkg'), // do not localize + (Key: 'csp'; Value: 'application/vnd.commonspace'), // do not localize + (Key: 'cdbcmsg'; Value: 'application/vnd.contact.cmsg'), // do not localize + (Key: 'cmc'; Value: 'application/vnd.cosmocaller'), // do not localize + (Key: 'clkx'; Value: 'application/vnd.crick.clicker'), // do not localize + (Key: 'clkk'; Value: 'application/vnd.crick.clicker.keyboard'), // do not localize + (Key: 'clkp'; Value: 'application/vnd.crick.clicker.palette'), // do not localize + (Key: 'clkt'; Value: 'application/vnd.crick.clicker.template'), // do not localize + (Key: 'clkw'; Value: 'application/vnd.crick.clicker.wordbank'), // do not localize + (Key: 'wbs'; Value: 'application/vnd.criticaltools.wbs+xml'), // do not localize + (Key: 'pml'; Value: 'application/vnd.ctc-posml'), // do not localize + (Key: 'ppd'; Value: 'application/vnd.cups-ppd'), // do not localize + (Key: 'car'; Value: 'application/vnd.curl.car'), // do not localize + (Key: 'pcurl'; Value: 'application/vnd.curl.pcurl'), // do not localize + (Key: 'dart'; Value: 'application/vnd.dart'), // do not localize + (Key: 'rdz'; Value: 'application/vnd.data-vision.rdz'), // do not localize + (Key: 'uvf'; Value: 'application/vnd.dece.data'), // do not localize + (Key: 'uvvf'; Value: 'application/vnd.dece.data'), // do not localize + (Key: 'uvd'; Value: 'application/vnd.dece.data'), // do not localize + (Key: 'uvvd'; Value: 'application/vnd.dece.data'), // do not localize + (Key: 'uvt'; Value: 'application/vnd.dece.ttml+xml'), // do not localize + (Key: 'uvvt'; Value: 'application/vnd.dece.ttml+xml'), // do not localize + (Key: 'uvx'; Value: 'application/vnd.dece.unspecified'), // do not localize + (Key: 'uvvx'; Value: 'application/vnd.dece.unspecified'), // do not localize + (Key: 'uvz'; Value: 'application/vnd.dece.zip'), // do not localize + (Key: 'uvvz'; Value: 'application/vnd.dece.zip'), // do not localize + (Key: 'fe_launch'; Value: 'application/vnd.denovo.fcselayout-link'), // do not localize + (Key: 'dna'; Value: 'application/vnd.dna'), // do not localize + (Key: 'mlp'; Value: 'application/vnd.dolby.mlp'), // do not localize + (Key: 'dpg'; Value: 'application/vnd.dpgraph'), // do not localize + (Key: 'dfac'; Value: 'application/vnd.dreamfactory'), // do not localize + (Key: 'kpxx'; Value: 'application/vnd.ds-keypoint'), // do not localize + (Key: 'ait'; Value: 'application/vnd.dvb.ait'), // do not localize + (Key: 'svc'; Value: 'application/vnd.dvb.service'), // do not localize + (Key: 'geo'; Value: 'application/vnd.dynageo'), // do not localize + (Key: 'mag'; Value: 'application/vnd.ecowin.chart'), // do not localize + (Key: 'nml'; Value: 'application/vnd.enliven'), // do not localize + (Key: 'esf'; Value: 'application/vnd.epson.esf'), // do not localize + (Key: 'msf'; Value: 'application/vnd.epson.msf'), // do not localize + (Key: 'qam'; Value: 'application/vnd.epson.quickanime'), // do not localize + (Key: 'slt'; Value: 'application/vnd.epson.salt'), // do not localize + (Key: 'ssf'; Value: 'application/vnd.epson.ssf'), // do not localize + (Key: 'es3'; Value: 'application/vnd.eszigno3+xml'), // do not localize + (Key: 'et3'; Value: 'application/vnd.eszigno3+xml'), // do not localize + (Key: 'ez2'; Value: 'application/vnd.ezpix-album'), // do not localize + (Key: 'ez3'; Value: 'application/vnd.ezpix-package'), // do not localize + (Key: 'fdf'; Value: 'application/vnd.fdf'), // do not localize + (Key: 'mseed'; Value: 'application/vnd.fdsn.mseed'), // do not localize + (Key: 'seed'; Value: 'application/vnd.fdsn.seed'), // do not localize + (Key: 'dataless'; Value: 'application/vnd.fdsn.seed'), // do not localize + (Key: 'gph'; Value: 'application/vnd.flographit'), // do not localize + (Key: 'ftc'; Value: 'application/vnd.fluxtime.clip'), // do not localize + (Key: 'fm'; Value: 'application/vnd.framemaker'), // do not localize + (Key: 'frame'; Value: 'application/vnd.framemaker'), // do not localize + (Key: 'maker'; Value: 'application/vnd.framemaker'), // do not localize + (Key: 'book'; Value: 'application/vnd.framemaker'), // do not localize + (Key: 'fnc'; Value: 'application/vnd.frogans.fnc'), // do not localize + (Key: 'ltf'; Value: 'application/vnd.frogans.ltf'), // do not localize + (Key: 'fsc'; Value: 'application/vnd.fsc.weblaunch'), // do not localize + (Key: 'oas'; Value: 'application/vnd.fujitsu.oasys'), // do not localize + (Key: 'oa2'; Value: 'application/vnd.fujitsu.oasys2'), // do not localize + (Key: 'oa3'; Value: 'application/vnd.fujitsu.oasys3'), // do not localize + (Key: 'fg5'; Value: 'application/vnd.fujitsu.oasysgp'), // do not localize + (Key: 'bh2'; Value: 'application/vnd.fujitsu.oasysprs'), // do not localize + (Key: 'ddd'; Value: 'application/vnd.fujixerox.ddd'), // do not localize + (Key: 'xdw'; Value: 'application/vnd.fujixerox.docuworks'), // do not localize + (Key: 'xbd'; Value: 'application/vnd.fujixerox.docuworks.binder'), // do not localize + (Key: 'fzs'; Value: 'application/vnd.fuzzysheet'), // do not localize + (Key: 'txd'; Value: 'application/vnd.genomatix.tuxedo'), // do not localize + (Key: 'ggb'; Value: 'application/vnd.geogebra.file'), // do not localize + (Key: 'ggt'; Value: 'application/vnd.geogebra.tool'), // do not localize + (Key: 'gex'; Value: 'application/vnd.geometry-explorer'), // do not localize + (Key: 'gre'; Value: 'application/vnd.geometry-explorer'), // do not localize + (Key: 'gxt'; Value: 'application/vnd.geonext'), // do not localize + (Key: 'g2w'; Value: 'application/vnd.geoplan'), // do not localize + (Key: 'g3w'; Value: 'application/vnd.geospace'), // do not localize + (Key: 'gmx'; Value: 'application/vnd.gmx'), // do not localize + (Key: 'kml'; Value: 'application/vnd.google-earth.kml+xml'), // do not localize + (Key: 'kmz'; Value: 'application/vnd.google-earth.kmz'), // do not localize + (Key: 'gqf'; Value: 'application/vnd.grafeq'), // do not localize + (Key: 'gqs'; Value: 'application/vnd.grafeq'), // do not localize + (Key: 'gac'; Value: 'application/vnd.groove-account'), // do not localize + (Key: 'ghf'; Value: 'application/vnd.groove-help'), // do not localize + (Key: 'gim'; Value: 'application/vnd.groove-identity-message'), // do not localize + (Key: 'grv'; Value: 'application/vnd.groove-injector'), // do not localize + (Key: 'gtm'; Value: 'application/vnd.groove-tool-message'), // do not localize + (Key: 'tpl'; Value: 'application/vnd.groove-tool-template'), // do not localize + (Key: 'vcg'; Value: 'application/vnd.groove-vcard'), // do not localize + (Key: 'hal'; Value: 'application/vnd.hal+xml'), // do not localize + (Key: 'zmm'; Value: 'application/vnd.handheld-entertainment+xml'), // do not localize + (Key: 'hbci'; Value: 'application/vnd.hbci'), // do not localize + (Key: 'les'; Value: 'application/vnd.hhe.lesson-player'), // do not localize + (Key: 'hpgl'; Value: 'application/vnd.hp-hpgl'), // do not localize + (Key: 'hpid'; Value: 'application/vnd.hp-hpid'), // do not localize + (Key: 'hps'; Value: 'application/vnd.hp-hps'), // do not localize + (Key: 'jlt'; Value: 'application/vnd.hp-jlyt'), // do not localize + (Key: 'pcl'; Value: 'application/vnd.hp-pcl'), // do not localize + (Key: 'pclxl'; Value: 'application/vnd.hp-pclxl'), // do not localize + (Key: 'sfd-hdstx'; Value: 'application/vnd.hydrostatix.sof-data'), // do not localize + (Key: 'mpy'; Value: 'application/vnd.ibm.minipay'), // do not localize + (Key: 'afp'; Value: 'application/vnd.ibm.modcap'), // do not localize + (Key: 'listafp'; Value: 'application/vnd.ibm.modcap'), // do not localize + (Key: 'list3820'; Value: 'application/vnd.ibm.modcap'), // do not localize + (Key: 'irm'; Value: 'application/vnd.ibm.rights-management'), // do not localize + (Key: 'sc'; Value: 'application/vnd.ibm.secure-container'), // do not localize + (Key: 'icc'; Value: 'application/vnd.iccprofile'), // do not localize + (Key: 'icm'; Value: 'application/vnd.iccprofile'), // do not localize + (Key: 'igl'; Value: 'application/vnd.igloader'), // do not localize + (Key: 'ivp'; Value: 'application/vnd.immervision-ivp'), // do not localize + (Key: 'ivu'; Value: 'application/vnd.immervision-ivu'), // do not localize + (Key: 'igm'; Value: 'application/vnd.insors.igm'), // do not localize + (Key: 'xpw'; Value: 'application/vnd.intercon.formnet'), // do not localize + (Key: 'xpx'; Value: 'application/vnd.intercon.formnet'), // do not localize + (Key: 'i2g'; Value: 'application/vnd.intergeo'), // do not localize + (Key: 'qbo'; Value: 'application/vnd.intu.qbo'), // do not localize + (Key: 'qfx'; Value: 'application/vnd.intu.qfx'), // do not localize + (Key: 'rcprofile'; Value: 'application/vnd.ipunplugged.rcprofile'), // do not localize + (Key: 'irp'; Value: 'application/vnd.irepository.package+xml'), // do not localize + (Key: 'xpr'; Value: 'application/vnd.is-xpr'), // do not localize + (Key: 'fcs'; Value: 'application/vnd.isac.fcs'), // do not localize + (Key: 'jam'; Value: 'application/vnd.jam'), // do not localize + (Key: 'rms'; Value: 'application/vnd.jcp.javame.midlet-rms'), // do not localize + (Key: 'jisp'; Value: 'application/vnd.jisp'), // do not localize + (Key: 'joda'; Value: 'application/vnd.joost.joda-archive'), // do not localize + (Key: 'ktz'; Value: 'application/vnd.kahootz'), // do not localize + (Key: 'ktr'; Value: 'application/vnd.kahootz'), // do not localize + (Key: 'karbon'; Value: 'application/vnd.kde.karbon'), // do not localize + (Key: 'chrt'; Value: 'application/vnd.kde.kchart'), // do not localize + (Key: 'kfo'; Value: 'application/vnd.kde.kformula'), // do not localize + (Key: 'flw'; Value: 'application/vnd.kde.kivio'), // do not localize + (Key: 'kon'; Value: 'application/vnd.kde.kontour'), // do not localize + (Key: 'kpr'; Value: 'application/vnd.kde.kpresenter'), // do not localize + (Key: 'kpt'; Value: 'application/vnd.kde.kpresenter'), // do not localize + (Key: 'ksp'; Value: 'application/vnd.kde.kspread'), // do not localize + (Key: 'kwd'; Value: 'application/vnd.kde.kword'), // do not localize + (Key: 'kwt'; Value: 'application/vnd.kde.kword'), // do not localize + (Key: 'htke'; Value: 'application/vnd.kenameaapp'), // do not localize + (Key: 'kia'; Value: 'application/vnd.kidspiration'), // do not localize + (Key: 'kne'; Value: 'application/vnd.kinar'), // do not localize + (Key: 'knp'; Value: 'application/vnd.kinar'), // do not localize + (Key: 'skp'; Value: 'application/vnd.koan'), // do not localize + (Key: 'skd'; Value: 'application/vnd.koan'), // do not localize + (Key: 'skt'; Value: 'application/vnd.koan'), // do not localize + (Key: 'skm'; Value: 'application/vnd.koan'), // do not localize + (Key: 'sse'; Value: 'application/vnd.kodak-descriptor'), // do not localize + (Key: 'lasxml'; Value: 'application/vnd.las.las+xml'), // do not localize + (Key: 'lbd'; Value: 'application/vnd.llamagraphics.life-balance.desktop'), // do not localize + (Key: 'lbe'; Value: 'application/vnd.llamagraphics.life-balance.exchange+xml'), // do not localize + (Key: '123'; Value: 'application/vnd.lotus-1-2-3'), // do not localize + (Key: 'apr'; Value: 'application/vnd.lotus-approach'), // do not localize + (Key: 'pre'; Value: 'application/vnd.lotus-freelance'), // do not localize + (Key: 'nsf'; Value: 'application/vnd.lotus-notes'), // do not localize + (Key: 'org'; Value: 'application/vnd.lotus-organizer'), // do not localize + (Key: 'scm'; Value: 'application/vnd.lotus-screencam'), // do not localize + (Key: 'lwp'; Value: 'application/vnd.lotus-wordpro'), // do not localize + (Key: 'portpkg'; Value: 'application/vnd.macports.portpkg'), // do not localize + (Key: 'mcd'; Value: 'application/vnd.mcd'), // do not localize + (Key: 'mc1'; Value: 'application/vnd.medcalcdata'), // do not localize + (Key: 'cdkey'; Value: 'application/vnd.mediastation.cdkey'), // do not localize + (Key: 'mwf'; Value: 'application/vnd.mfer'), // do not localize + (Key: 'mfm'; Value: 'application/vnd.mfmp'), // do not localize + (Key: 'flo'; Value: 'application/vnd.micrografx.flo'), // do not localize + (Key: 'igx'; Value: 'application/vnd.micrografx.igx'), // do not localize + (Key: 'mif'; Value: 'application/vnd.mif'), // do not localize + (Key: 'daf'; Value: 'application/vnd.mobius.daf'), // do not localize + (Key: 'dis'; Value: 'application/vnd.mobius.dis'), // do not localize + (Key: 'mbk'; Value: 'application/vnd.mobius.mbk'), // do not localize + (Key: 'mqy'; Value: 'application/vnd.mobius.mqy'), // do not localize + (Key: 'msl'; Value: 'application/vnd.mobius.msl'), // do not localize + (Key: 'plc'; Value: 'application/vnd.mobius.plc'), // do not localize + (Key: 'txf'; Value: 'application/vnd.mobius.txf'), // do not localize + (Key: 'mpn'; Value: 'application/vnd.mophun.application'), // do not localize + (Key: 'mpc'; Value: 'application/vnd.mophun.certificate'), // do not localize + (Key: 'xul'; Value: 'application/vnd.mozilla.xul+xml'), // do not localize + (Key: 'cil'; Value: 'application/vnd.ms-artgalry'), // do not localize + (Key: 'cab'; Value: 'application/vnd.ms-cab-compressed'), // do not localize + (Key: 'xls'; Value: 'application/vnd.ms-excel'), // do not localize + (Key: 'xlm'; Value: 'application/vnd.ms-excel'), // do not localize + (Key: 'xla'; Value: 'application/vnd.ms-excel'), // do not localize + (Key: 'xlc'; Value: 'application/vnd.ms-excel'), // do not localize + (Key: 'xlt'; Value: 'application/vnd.ms-excel'), // do not localize + (Key: 'xlw'; Value: 'application/vnd.ms-excel'), // do not localize + (Key: 'xlam'; Value: 'application/vnd.ms-excel.addin.macroenabled.12'), // do not localize + (Key: 'xlsb'; Value: 'application/vnd.ms-excel.sheet.binary.macroenabled.12'), // do not localize + (Key: 'xlsm'; Value: 'application/vnd.ms-excel.sheet.macroenabled.12'), // do not localize + (Key: 'xltm'; Value: 'application/vnd.ms-excel.template.macroenabled.12'), // do not localize + (Key: 'eot'; Value: 'application/vnd.ms-fontobject'), // do not localize + (Key: 'chm'; Value: 'application/vnd.ms-htmlhelp'), // do not localize + (Key: 'ims'; Value: 'application/vnd.ms-ims'), // do not localize + (Key: 'lrm'; Value: 'application/vnd.ms-lrm'), // do not localize + (Key: 'thmx'; Value: 'application/vnd.ms-officetheme'), // do not localize + (Key: 'cat'; Value: 'application/vnd.ms-pki.seccat'), // do not localize + (Key: 'stl'; Value: 'application/vnd.ms-pki.stl'), // do not localize + (Key: 'ppt'; Value: 'application/vnd.ms-powerpoint'), // do not localize + (Key: 'pps'; Value: 'application/vnd.ms-powerpoint'), // do not localize + (Key: 'pot'; Value: 'application/vnd.ms-powerpoint'), // do not localize + (Key: 'ppam'; Value: 'application/vnd.ms-powerpoint.addin.macroenabled.12'), // do not localize + (Key: 'pptm'; Value: 'application/vnd.ms-powerpoint.presentation.macroenabled.12'), // do not localize + (Key: 'sldm'; Value: 'application/vnd.ms-powerpoint.slide.macroenabled.12'), // do not localize + (Key: 'ppsm'; Value: 'application/vnd.ms-powerpoint.slideshow.macroenabled.12'), // do not localize + (Key: 'potm'; Value: 'application/vnd.ms-powerpoint.template.macroenabled.12'), // do not localize + (Key: 'mpp'; Value: 'application/vnd.ms-project'), // do not localize + (Key: 'mpt'; Value: 'application/vnd.ms-project'), // do not localize + (Key: 'docm'; Value: 'application/vnd.ms-word.document.macroenabled.12'), // do not localize + (Key: 'dotm'; Value: 'application/vnd.ms-word.template.macroenabled.12'), // do not localize + (Key: 'wps'; Value: 'application/vnd.ms-works'), // do not localize + (Key: 'wks'; Value: 'application/vnd.ms-works'), // do not localize + (Key: 'wcm'; Value: 'application/vnd.ms-works'), // do not localize + (Key: 'wdb'; Value: 'application/vnd.ms-works'), // do not localize + (Key: 'wpl'; Value: 'application/vnd.ms-wpl'), // do not localize + (Key: 'xps'; Value: 'application/vnd.ms-xpsdocument'), // do not localize + (Key: 'mseq'; Value: 'application/vnd.mseq'), // do not localize + (Key: 'mus'; Value: 'application/vnd.musician'), // do not localize + (Key: 'msty'; Value: 'application/vnd.muvee.style'), // do not localize + (Key: 'taglet'; Value: 'application/vnd.mynfc'), // do not localize + (Key: 'nlu'; Value: 'application/vnd.neurolanguage.nlu'), // do not localize + (Key: 'ntf'; Value: 'application/vnd.nitf'), // do not localize + (Key: 'nitf'; Value: 'application/vnd.nitf'), // do not localize + (Key: 'nnd'; Value: 'application/vnd.noblenet-directory'), // do not localize + (Key: 'nns'; Value: 'application/vnd.noblenet-sealer'), // do not localize + (Key: 'nnw'; Value: 'application/vnd.noblenet-web'), // do not localize + (Key: 'ngdat'; Value: 'application/vnd.nokia.n-gage.data'), // do not localize + (Key: 'n-gage'; Value: 'application/vnd.nokia.n-gage.symbian.install'), // do not localize + (Key: 'rpst'; Value: 'application/vnd.nokia.radio-preset'), // do not localize + (Key: 'rpss'; Value: 'application/vnd.nokia.radio-presets'), // do not localize + (Key: 'edm'; Value: 'application/vnd.novadigm.edm'), // do not localize + (Key: 'edx'; Value: 'application/vnd.novadigm.edx'), // do not localize + (Key: 'ext'; Value: 'application/vnd.novadigm.ext'), // do not localize + (Key: 'odc'; Value: 'application/vnd.oasis.opendocument.chart'), // do not localize + (Key: 'otc'; Value: 'application/vnd.oasis.opendocument.chart-template'), // do not localize + (Key: 'odb'; Value: 'application/vnd.oasis.opendocument.database'), // do not localize + (Key: 'odf'; Value: 'application/vnd.oasis.opendocument.formula'), // do not localize + (Key: 'odft'; Value: 'application/vnd.oasis.opendocument.formula-template'), // do not localize + (Key: 'odg'; Value: 'application/vnd.oasis.opendocument.graphics'), // do not localize + (Key: 'otg'; Value: 'application/vnd.oasis.opendocument.graphics-template'), // do not localize + (Key: 'odi'; Value: 'application/vnd.oasis.opendocument.image'), // do not localize + (Key: 'oti'; Value: 'application/vnd.oasis.opendocument.image-template'), // do not localize + (Key: 'odp'; Value: 'application/vnd.oasis.opendocument.presentation'), // do not localize + (Key: 'otp'; Value: 'application/vnd.oasis.opendocument.presentation-template'), // do not localize + (Key: 'ods'; Value: 'application/vnd.oasis.opendocument.spreadsheet'), // do not localize + (Key: 'ots'; Value: 'application/vnd.oasis.opendocument.spreadsheet-template'), // do not localize + (Key: 'odt'; Value: 'application/vnd.oasis.opendocument.text'), // do not localize + (Key: 'odm'; Value: 'application/vnd.oasis.opendocument.text-master'), // do not localize + (Key: 'ott'; Value: 'application/vnd.oasis.opendocument.text-template'), // do not localize + (Key: 'oth'; Value: 'application/vnd.oasis.opendocument.text-web'), // do not localize + (Key: 'xo'; Value: 'application/vnd.olpc-sugar'), // do not localize + (Key: 'dd2'; Value: 'application/vnd.oma.dd2+xml'), // do not localize + (Key: 'oxt'; Value: 'application/vnd.openofficeorg.extension'), // do not localize + (Key: 'pptx'; Value: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'), // do not localize + (Key: 'sldx'; Value: 'application/vnd.openxmlformats-officedocument.presentationml.slide'), // do not localize + (Key: 'ppsx'; Value: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow'), // do not localize + (Key: 'potx'; Value: 'application/vnd.openxmlformats-officedocument.presentationml.template'), // do not localize + (Key: 'xlsx'; Value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), // do not localize + (Key: 'xltx'; Value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'), // do not localize + (Key: 'docx'; Value: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'), // do not localize + (Key: 'dotx'; Value: 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'), // do not localize + (Key: 'mgp'; Value: 'application/vnd.osgeo.mapguide.package'), // do not localize + (Key: 'dp'; Value: 'application/vnd.osgi.dp'), // do not localize + (Key: 'esa'; Value: 'application/vnd.osgi.subsystem'), // do not localize + (Key: 'pdb'; Value: 'application/vnd.palm'), // do not localize + (Key: 'pqa'; Value: 'application/vnd.palm'), // do not localize + (Key: 'oprc'; Value: 'application/vnd.palm'), // do not localize + (Key: 'paw'; Value: 'application/vnd.pawaafile'), // do not localize + (Key: 'str'; Value: 'application/vnd.pg.format'), // do not localize + (Key: 'ei6'; Value: 'application/vnd.pg.osasli'), // do not localize + (Key: 'efif'; Value: 'application/vnd.picsel'), // do not localize + (Key: 'wg'; Value: 'application/vnd.pmi.widget'), // do not localize + (Key: 'plf'; Value: 'application/vnd.pocketlearn'), // do not localize + (Key: 'pbd'; Value: 'application/vnd.powerbuilder6'), // do not localize + (Key: 'box'; Value: 'application/vnd.previewsystems.box'), // do not localize + (Key: 'mgz'; Value: 'application/vnd.proteus.magazine'), // do not localize + (Key: 'qps'; Value: 'application/vnd.publishare-delta-tree'), // do not localize + (Key: 'ptid'; Value: 'application/vnd.pvi.ptid1'), // do not localize + (Key: 'qxd'; Value: 'application/vnd.quark.quarkxpress'), // do not localize + (Key: 'qxt'; Value: 'application/vnd.quark.quarkxpress'), // do not localize + (Key: 'qwd'; Value: 'application/vnd.quark.quarkxpress'), // do not localize + (Key: 'qwt'; Value: 'application/vnd.quark.quarkxpress'), // do not localize + (Key: 'qxl'; Value: 'application/vnd.quark.quarkxpress'), // do not localize + (Key: 'qxb'; Value: 'application/vnd.quark.quarkxpress'), // do not localize + (Key: 'bed'; Value: 'application/vnd.realvnc.bed'), // do not localize + (Key: 'mxl'; Value: 'application/vnd.recordare.musicxml'), // do not localize + (Key: 'musicxml'; Value: 'application/vnd.recordare.musicxml+xml'), // do not localize + (Key: 'cryptonote'; Value: 'application/vnd.rig.cryptonote'), // do not localize + (Key: 'cod'; Value: 'application/vnd.rim.cod'), // do not localize + (Key: 'rm'; Value: 'application/vnd.rn-realmedia'), // do not localize + (Key: 'rmvb'; Value: 'application/vnd.rn-realmedia-vbr'), // do not localize + (Key: 'link66'; Value: 'application/vnd.route66.link66+xml'), // do not localize + (Key: 'st'; Value: 'application/vnd.sailingtracker.track'), // do not localize + (Key: 'see'; Value: 'application/vnd.seemail'), // do not localize + (Key: 'sema'; Value: 'application/vnd.sema'), // do not localize + (Key: 'semd'; Value: 'application/vnd.semd'), // do not localize + (Key: 'semf'; Value: 'application/vnd.semf'), // do not localize + (Key: 'ifm'; Value: 'application/vnd.shana.informed.formdata'), // do not localize + (Key: 'itp'; Value: 'application/vnd.shana.informed.formtemplate'), // do not localize + (Key: 'iif'; Value: 'application/vnd.shana.informed.interchange'), // do not localize + (Key: 'ipk'; Value: 'application/vnd.shana.informed.package'), // do not localize + (Key: 'twd'; Value: 'application/vnd.simtech-mindmapper'), // do not localize + (Key: 'twds'; Value: 'application/vnd.simtech-mindmapper'), // do not localize + (Key: 'mmf'; Value: 'application/vnd.smaf'), // do not localize + (Key: 'teacher'; Value: 'application/vnd.smart.teacher'), // do not localize + (Key: 'sdkm'; Value: 'application/vnd.solent.sdkm+xml'), // do not localize + (Key: 'sdkd'; Value: 'application/vnd.solent.sdkm+xml'), // do not localize + (Key: 'dxp'; Value: 'application/vnd.spotfire.dxp'), // do not localize + (Key: 'sfs'; Value: 'application/vnd.spotfire.sfs'), // do not localize + (Key: 'sdc'; Value: 'application/vnd.stardivision.calc'), // do not localize + (Key: 'sda'; Value: 'application/vnd.stardivision.draw'), // do not localize + (Key: 'sdd'; Value: 'application/vnd.stardivision.impress'), // do not localize + (Key: 'smf'; Value: 'application/vnd.stardivision.math'), // do not localize + (Key: 'sdw'; Value: 'application/vnd.stardivision.writer'), // do not localize + (Key: 'vor'; Value: 'application/vnd.stardivision.writer'), // do not localize + (Key: 'sgl'; Value: 'application/vnd.stardivision.writer-global'), // do not localize + (Key: 'smzip'; Value: 'application/vnd.stepmania.package'), // do not localize + (Key: 'sm'; Value: 'application/vnd.stepmania.stepchart'), // do not localize + (Key: 'sxc'; Value: 'application/vnd.sun.xml.calc'), // do not localize + (Key: 'stc'; Value: 'application/vnd.sun.xml.calc.template'), // do not localize + (Key: 'sxd'; Value: 'application/vnd.sun.xml.draw'), // do not localize + (Key: 'std'; Value: 'application/vnd.sun.xml.draw.template'), // do not localize + (Key: 'sxi'; Value: 'application/vnd.sun.xml.impress'), // do not localize + (Key: 'sti'; Value: 'application/vnd.sun.xml.impress.template'), // do not localize + (Key: 'sxm'; Value: 'application/vnd.sun.xml.math'), // do not localize + (Key: 'sxw'; Value: 'application/vnd.sun.xml.writer'), // do not localize + (Key: 'sxg'; Value: 'application/vnd.sun.xml.writer.global'), // do not localize + (Key: 'stw'; Value: 'application/vnd.sun.xml.writer.template'), // do not localize + (Key: 'sus'; Value: 'application/vnd.sus-calendar'), // do not localize + (Key: 'susp'; Value: 'application/vnd.sus-calendar'), // do not localize + (Key: 'svd'; Value: 'application/vnd.svd'), // do not localize + (Key: 'sis'; Value: 'application/vnd.symbian.install'), // do not localize + (Key: 'sisx'; Value: 'application/vnd.symbian.install'), // do not localize + (Key: 'xsm'; Value: 'application/vnd.syncml+xml'), // do not localize + (Key: 'bdm'; Value: 'application/vnd.syncml.dm+wbxml'), // do not localize + (Key: 'xdm'; Value: 'application/vnd.syncml.dm+xml'), // do not localize + (Key: 'tao'; Value: 'application/vnd.tao.intent-module-archive'), // do not localize + (Key: 'pcap'; Value: 'application/vnd.tcpdump.pcap'), // do not localize + (Key: 'cap'; Value: 'application/vnd.tcpdump.pcap'), // do not localize + (Key: 'dmp'; Value: 'application/vnd.tcpdump.pcap'), // do not localize + (Key: 'tmo'; Value: 'application/vnd.tmobile-livetv'), // do not localize + (Key: 'tpt'; Value: 'application/vnd.trid.tpt'), // do not localize + (Key: 'mxs'; Value: 'application/vnd.triscape.mxs'), // do not localize + (Key: 'tra'; Value: 'application/vnd.trueapp'), // do not localize + (Key: 'ufd'; Value: 'application/vnd.ufdl'), // do not localize + (Key: 'ufdl'; Value: 'application/vnd.ufdl'), // do not localize + (Key: 'utz'; Value: 'application/vnd.uiq.theme'), // do not localize + (Key: 'umj'; Value: 'application/vnd.umajin'), // do not localize + (Key: 'unityweb'; Value: 'application/vnd.unity'), // do not localize + (Key: 'uoml'; Value: 'application/vnd.uoml+xml'), // do not localize + (Key: 'vcx'; Value: 'application/vnd.vcx'), // do not localize + (Key: 'vsd'; Value: 'application/vnd.visio'), // do not localize + (Key: 'vst'; Value: 'application/vnd.visio'), // do not localize + (Key: 'vss'; Value: 'application/vnd.visio'), // do not localize + (Key: 'vsw'; Value: 'application/vnd.visio'), // do not localize + (Key: 'vis'; Value: 'application/vnd.visionary'), // do not localize + (Key: 'vsf'; Value: 'application/vnd.vsf'), // do not localize + (Key: 'wbxml'; Value: 'application/vnd.wap.wbxml'), // do not localize + (Key: 'wmlc'; Value: 'application/vnd.wap.wmlc'), // do not localize + (Key: 'wmlsc'; Value: 'application/vnd.wap.wmlscriptc'), // do not localize + (Key: 'wtb'; Value: 'application/vnd.webturbo'), // do not localize + (Key: 'nbp'; Value: 'application/vnd.wolfram.player'), // do not localize + (Key: 'wpd'; Value: 'application/vnd.wordperfect'), // do not localize + (Key: 'wqd'; Value: 'application/vnd.wqd'), // do not localize + (Key: 'stf'; Value: 'application/vnd.wt.stf'), // do not localize + (Key: 'xar'; Value: 'application/vnd.xara'), // do not localize + (Key: 'xfdl'; Value: 'application/vnd.xfdl'), // do not localize + (Key: 'hvd'; Value: 'application/vnd.yamaha.hv-dic'), // do not localize + (Key: 'hvs'; Value: 'application/vnd.yamaha.hv-script'), // do not localize + (Key: 'hvp'; Value: 'application/vnd.yamaha.hv-voice'), // do not localize + (Key: 'osf'; Value: 'application/vnd.yamaha.openscoreformat'), // do not localize + (Key: 'osfpvg'; Value: 'application/vnd.yamaha.openscoreformat.osfpvg+xml'), // do not localize + (Key: 'saf'; Value: 'application/vnd.yamaha.smaf-audio'), // do not localize + (Key: 'spf'; Value: 'application/vnd.yamaha.smaf-phrase'), // do not localize + (Key: 'cmp'; Value: 'application/vnd.yellowriver-custom-menu'), // do not localize + (Key: 'zir'; Value: 'application/vnd.zul'), // do not localize + (Key: 'zirz'; Value: 'application/vnd.zul'), // do not localize + (Key: 'zaz'; Value: 'application/vnd.zzazz.deck+xml'), // do not localize + (Key: 'vxml'; Value: 'application/voicexml+xml'), // do not localize + (Key: 'wgt'; Value: 'application/widget'), // do not localize + (Key: 'hlp'; Value: 'application/winhlp'), // do not localize + (Key: 'wsdl'; Value: 'application/wsdl+xml'), // do not localize + (Key: 'wspolicy'; Value: 'application/wspolicy+xml'), // do not localize + (Key: '7z'; Value: 'application/x-7z-compressed'), // do not localize + (Key: 'abw'; Value: 'application/x-abiword'), // do not localize + (Key: 'ace'; Value: 'application/x-ace-compressed'), // do not localize + (Key: 'dmg'; Value: 'application/x-apple-diskimage'), // do not localize + (Key: 'aab'; Value: 'application/x-authorware-bin'), // do not localize + (Key: 'x32'; Value: 'application/x-authorware-bin'), // do not localize + (Key: 'u32'; Value: 'application/x-authorware-bin'), // do not localize + (Key: 'vox'; Value: 'application/x-authorware-bin'), // do not localize + (Key: 'aam'; Value: 'application/x-authorware-map'), // do not localize + (Key: 'aas'; Value: 'application/x-authorware-seg'), // do not localize + (Key: 'bcpio'; Value: 'application/x-bcpio'), // do not localize + (Key: 'torrent'; Value: 'application/x-bittorrent'), // do not localize + (Key: 'blb'; Value: 'application/x-blorb'), // do not localize + (Key: 'blorb'; Value: 'application/x-blorb'), // do not localize + (Key: 'bz'; Value: 'application/x-bzip'), // do not localize + (Key: 'bz2'; Value: 'application/x-bzip2'), // do not localize + (Key: 'boz'; Value: 'application/x-bzip2'), // do not localize + (Key: 'cbr'; Value: 'application/x-cbr'), // do not localize + (Key: 'cba'; Value: 'application/x-cbr'), // do not localize + (Key: 'cbt'; Value: 'application/x-cbr'), // do not localize + (Key: 'cbz'; Value: 'application/x-cbr'), // do not localize + (Key: 'cb7'; Value: 'application/x-cbr'), // do not localize + (Key: 'vcd'; Value: 'application/x-cdlink'), // do not localize + (Key: 'cfs'; Value: 'application/x-cfs-compressed'), // do not localize + (Key: 'chat'; Value: 'application/x-chat'), // do not localize + (Key: 'pgn'; Value: 'application/x-chess-pgn'), // do not localize + (Key: 'nsc'; Value: 'application/x-conference'), // do not localize + (Key: 'cpio'; Value: 'application/x-cpio'), // do not localize + (Key: 'csh'; Value: 'application/x-csh'), // do not localize + (Key: 'deb'; Value: 'application/x-debian-package'), // do not localize + (Key: 'udeb'; Value: 'application/x-debian-package'), // do not localize + (Key: 'dgc'; Value: 'application/x-dgc-compressed'), // do not localize + (Key: 'dir'; Value: 'application/x-director'), // do not localize + (Key: 'dcr'; Value: 'application/x-director'), // do not localize + (Key: 'dxr'; Value: 'application/x-director'), // do not localize + (Key: 'cst'; Value: 'application/x-director'), // do not localize + (Key: 'cct'; Value: 'application/x-director'), // do not localize + (Key: 'cxt'; Value: 'application/x-director'), // do not localize + (Key: 'w3d'; Value: 'application/x-director'), // do not localize + (Key: 'fgd'; Value: 'application/x-director'), // do not localize + (Key: 'swa'; Value: 'application/x-director'), // do not localize + (Key: 'wad'; Value: 'application/x-doom'), // do not localize + (Key: 'ncx'; Value: 'application/x-dtbncx+xml'), // do not localize + (Key: 'dtb'; Value: 'application/x-dtbook+xml'), // do not localize + (Key: 'res'; Value: 'application/x-dtbresource+xml'), // do not localize + (Key: 'dvi'; Value: 'application/x-dvi'), // do not localize + (Key: 'evy'; Value: 'application/x-envoy'), // do not localize + (Key: 'eva'; Value: 'application/x-eva'), // do not localize + (Key: 'bdf'; Value: 'application/x-font-bdf'), // do not localize + (Key: 'gsf'; Value: 'application/x-font-ghostscript'), // do not localize + (Key: 'psf'; Value: 'application/x-font-linux-psf'), // do not localize + (Key: 'otf'; Value: 'application/x-font-otf'), // do not localize + (Key: 'pcf'; Value: 'application/x-font-pcf'), // do not localize + (Key: 'snf'; Value: 'application/x-font-snf'), // do not localize + (Key: 'ttf'; Value: 'application/x-font-ttf'), // do not localize + (Key: 'ttc'; Value: 'application/x-font-ttf'), // do not localize + (Key: 'pfa'; Value: 'application/x-font-type1'), // do not localize + (Key: 'pfb'; Value: 'application/x-font-type1'), // do not localize + (Key: 'pfm'; Value: 'application/x-font-type1'), // do not localize + (Key: 'afm'; Value: 'application/x-font-type1'), // do not localize + (Key: 'woff'; Value: 'application/x-font-woff'), // do not localize + (Key: 'arc'; Value: 'application/x-freearc'), // do not localize + (Key: 'spl'; Value: 'application/x-futuresplash'), // do not localize + (Key: 'gca'; Value: 'application/x-gca-compressed'), // do not localize + (Key: 'ulx'; Value: 'application/x-glulx'), // do not localize + (Key: 'gnumeric'; Value: 'application/x-gnumeric'), // do not localize + (Key: 'gramps'; Value: 'application/x-gramps-xml'), // do not localize + (Key: 'gtar'; Value: 'application/x-gtar'), // do not localize + (Key: 'hdf'; Value: 'application/x-hdf'), // do not localize + (Key: 'install'; Value: 'application/x-install-instructions'), // do not localize + (Key: 'iso'; Value: 'application/x-iso9660-image'), // do not localize + (Key: 'jnlp'; Value: 'application/x-java-jnlp-file'), // do not localize + (Key: 'latex'; Value: 'application/x-latex'), // do not localize + (Key: 'lzh'; Value: 'application/x-lzh-compressed'), // do not localize + (Key: 'lha'; Value: 'application/x-lzh-compressed'), // do not localize + (Key: 'mie'; Value: 'application/x-mie'), // do not localize + (Key: 'prc'; Value: 'application/x-mobipocket-ebook'), // do not localize + (Key: 'mobi'; Value: 'application/x-mobipocket-ebook'), // do not localize + (Key: 'application'; Value: 'application/x-ms-application'), // do not localize + (Key: 'lnk'; Value: 'application/x-ms-shortcut'), // do not localize + (Key: 'wmd'; Value: 'application/x-ms-wmd'), // do not localize + (Key: 'wmz'; Value: 'application/x-ms-wmz'), // do not localize + (Key: 'xbap'; Value: 'application/x-ms-xbap'), // do not localize + (Key: 'mdb'; Value: 'application/x-msaccess'), // do not localize + (Key: 'obd'; Value: 'application/x-msbinder'), // do not localize + (Key: 'crd'; Value: 'application/x-mscardfile'), // do not localize + (Key: 'clp'; Value: 'application/x-msclip'), // do not localize + (Key: 'exe'; Value: 'application/x-msdownload'), // do not localize + (Key: 'dll'; Value: 'application/x-msdownload'), // do not localize + (Key: 'com'; Value: 'application/x-msdownload'), // do not localize + (Key: 'bat'; Value: 'application/x-msdownload'), // do not localize + (Key: 'msi'; Value: 'application/x-msdownload'), // do not localize + (Key: 'mvb'; Value: 'application/x-msmediaview'), // do not localize + (Key: 'm13'; Value: 'application/x-msmediaview'), // do not localize + (Key: 'm14'; Value: 'application/x-msmediaview'), // do not localize + (Key: 'wmf'; Value: 'application/x-msmetafile'), // do not localize + (Key: 'wmz'; Value: 'application/x-msmetafile'), // do not localize + (Key: 'emf'; Value: 'application/x-msmetafile'), // do not localize + (Key: 'emz'; Value: 'application/x-msmetafile'), // do not localize + (Key: 'mny'; Value: 'application/x-msmoney'), // do not localize + (Key: 'pub'; Value: 'application/x-mspublisher'), // do not localize + (Key: 'scd'; Value: 'application/x-msschedule'), // do not localize + (Key: 'trm'; Value: 'application/x-msterminal'), // do not localize + (Key: 'wri'; Value: 'application/x-mswrite'), // do not localize + (Key: 'nc'; Value: 'application/x-netcdf'), // do not localize + (Key: 'cdf'; Value: 'application/x-netcdf'), // do not localize + (Key: 'nzb'; Value: 'application/x-nzb'), // do not localize + (Key: 'p12'; Value: 'application/x-pkcs12'), // do not localize + (Key: 'pfx'; Value: 'application/x-pkcs12'), // do not localize + (Key: 'p7b'; Value: 'application/x-pkcs7-certificates'), // do not localize + (Key: 'spc'; Value: 'application/x-pkcs7-certificates'), // do not localize + (Key: 'p7r'; Value: 'application/x-pkcs7-certreqresp'), // do not localize + (Key: 'rar'; Value: 'application/x-rar-compressed'), // do not localize + (Key: 'ris'; Value: 'application/x-research-info-systems'), // do not localize + (Key: 'sh'; Value: 'application/x-sh'), // do not localize + (Key: 'shar'; Value: 'application/x-shar'), // do not localize + (Key: 'swf'; Value: 'application/x-shockwave-flash'), // do not localize + (Key: 'xap'; Value: 'application/x-silverlight-app'), // do not localize + (Key: 'sql'; Value: 'application/x-sql'), // do not localize + (Key: 'sit'; Value: 'application/x-stuffit'), // do not localize + (Key: 'sitx'; Value: 'application/x-stuffitx'), // do not localize + (Key: 'srt'; Value: 'application/x-subrip'), // do not localize + (Key: 'sv4cpio'; Value: 'application/x-sv4cpio'), // do not localize + (Key: 'sv4crc'; Value: 'application/x-sv4crc'), // do not localize + (Key: 't3'; Value: 'application/x-t3vm-image'), // do not localize + (Key: 'gam'; Value: 'application/x-tads'), // do not localize + (Key: 'tar'; Value: 'application/x-tar'), // do not localize + (Key: 'tcl'; Value: 'application/x-tcl'), // do not localize + (Key: 'tex'; Value: 'application/x-tex'), // do not localize + (Key: 'tfm'; Value: 'application/x-tex-tfm'), // do not localize + (Key: 'texinfo'; Value: 'application/x-texinfo'), // do not localize + (Key: 'texi'; Value: 'application/x-texinfo'), // do not localize + (Key: 'obj'; Value: 'application/x-tgif'), // do not localize + (Key: 'ustar'; Value: 'application/x-ustar'), // do not localize + (Key: 'src'; Value: 'application/x-wais-source'), // do not localize + (Key: 'der'; Value: 'application/x-x509-ca-cert'), // do not localize + (Key: 'crt'; Value: 'application/x-x509-ca-cert'), // do not localize + (Key: 'fig'; Value: 'application/x-xfig'), // do not localize + (Key: 'xlf'; Value: 'application/x-xliff+xml'), // do not localize + (Key: 'xpi'; Value: 'application/x-xpinstall'), // do not localize + (Key: 'xz'; Value: 'application/x-xz'), // do not localize + (Key: 'z1'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z2'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z3'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z4'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z5'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z6'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z7'; Value: 'application/x-zmachine'), // do not localize + (Key: 'z8'; Value: 'application/x-zmachine'), // do not localize + (Key: 'xaml'; Value: 'application/xaml+xml'), // do not localize + (Key: 'xdf'; Value: 'application/xcap-diff+xml'), // do not localize + (Key: 'xenc'; Value: 'application/xenc+xml'), // do not localize + (Key: 'xhtml'; Value: 'application/xhtml+xml'), // do not localize + (Key: 'xht'; Value: 'application/xhtml+xml'), // do not localize + (Key: 'xml'; Value: 'application/xml'), // do not localize + (Key: 'xsl'; Value: 'application/xml'), // do not localize + (Key: 'dtd'; Value: 'application/xml-dtd'), // do not localize + (Key: 'xop'; Value: 'application/xop+xml'), // do not localize + (Key: 'xpl'; Value: 'application/xproc+xml'), // do not localize + (Key: 'xslt'; Value: 'application/xslt+xml'), // do not localize + (Key: 'xspf'; Value: 'application/xspf+xml'), // do not localize + (Key: 'mxml'; Value: 'application/xv+xml'), // do not localize + (Key: 'xhvml'; Value: 'application/xv+xml'), // do not localize + (Key: 'xvml'; Value: 'application/xv+xml'), // do not localize + (Key: 'xvm'; Value: 'application/xv+xml'), // do not localize + (Key: 'yang'; Value: 'application/yang'), // do not localize + (Key: 'yin'; Value: 'application/yin+xml'), // do not localize + (Key: 'zip'; Value: 'application/zip'), // do not localize + (Key: 'adp'; Value: 'audio/adpcm'), // do not localize + (Key: 'au'; Value: 'audio/basic'), // do not localize + (Key: 'snd'; Value: 'audio/basic'), // do not localize + (Key: 'mid'; Value: 'audio/midi'), // do not localize + (Key: 'midi'; Value: 'audio/midi'), // do not localize + (Key: 'kar'; Value: 'audio/midi'), // do not localize + (Key: 'rmi'; Value: 'audio/midi'), // do not localize + (Key: 'mp4a'; Value: 'audio/mp4'), // do not localize + (Key: 'mpga'; Value: 'audio/mpeg'), // do not localize + (Key: 'mp2'; Value: 'audio/mpeg'), // do not localize + (Key: 'mp2a'; Value: 'audio/mpeg'), // do not localize + (Key: 'mp3'; Value: 'audio/mpeg'), // do not localize + (Key: 'm2a'; Value: 'audio/mpeg'), // do not localize + (Key: 'm3a'; Value: 'audio/mpeg'), // do not localize + (Key: 'oga'; Value: 'audio/ogg'), // do not localize + (Key: 'ogg'; Value: 'audio/ogg'), // do not localize + (Key: 'spx'; Value: 'audio/ogg'), // do not localize + (Key: 's3m'; Value: 'audio/s3m'), // do not localize + (Key: 'sil'; Value: 'audio/silk'), // do not localize + (Key: 'uva'; Value: 'audio/vnd.dece.audio'), // do not localize + (Key: 'uvva'; Value: 'audio/vnd.dece.audio'), // do not localize + (Key: 'eol'; Value: 'audio/vnd.digital-winds'), // do not localize + (Key: 'dra'; Value: 'audio/vnd.dra'), // do not localize + (Key: 'dts'; Value: 'audio/vnd.dts'), // do not localize + (Key: 'dtshd'; Value: 'audio/vnd.dts.hd'), // do not localize + (Key: 'lvp'; Value: 'audio/vnd.lucent.voice'), // do not localize + (Key: 'pya'; Value: 'audio/vnd.ms-playready.media.pya'), // do not localize + (Key: 'ecelp4800'; Value: 'audio/vnd.nuera.ecelp4800'), // do not localize + (Key: 'ecelp7470'; Value: 'audio/vnd.nuera.ecelp7470'), // do not localize + (Key: 'ecelp9600'; Value: 'audio/vnd.nuera.ecelp9600'), // do not localize + (Key: 'rip'; Value: 'audio/vnd.rip'), // do not localize + (Key: 'weba'; Value: 'audio/webm'), // do not localize + (Key: 'aac'; Value: 'audio/x-aac'), // do not localize + (Key: 'aif'; Value: 'audio/x-aiff'), // do not localize + (Key: 'aiff'; Value: 'audio/x-aiff'), // do not localize + (Key: 'aifc'; Value: 'audio/x-aiff'), // do not localize + (Key: 'caf'; Value: 'audio/x-caf'), // do not localize + (Key: 'flac'; Value: 'audio/x-flac'), // do not localize + (Key: 'mka'; Value: 'audio/x-matroska'), // do not localize + (Key: 'm3u'; Value: 'audio/x-mpegurl'), // do not localize + (Key: 'wax'; Value: 'audio/x-ms-wax'), // do not localize + (Key: 'wma'; Value: 'audio/x-ms-wma'), // do not localize + (Key: 'ram'; Value: 'audio/x-pn-realaudio'), // do not localize + (Key: 'ra'; Value: 'audio/x-pn-realaudio'), // do not localize + (Key: 'rmp'; Value: 'audio/x-pn-realaudio-plugin'), // do not localize + (Key: 'wav'; Value: 'audio/x-wav'), // do not localize + (Key: 'xm'; Value: 'audio/xm'), // do not localize + (Key: 'cdx'; Value: 'chemical/x-cdx'), // do not localize + (Key: 'cif'; Value: 'chemical/x-cif'), // do not localize + (Key: 'cmdf'; Value: 'chemical/x-cmdf'), // do not localize + (Key: 'cml'; Value: 'chemical/x-cml'), // do not localize + (Key: 'csml'; Value: 'chemical/x-csml'), // do not localize + (Key: 'xyz'; Value: 'chemical/x-xyz'), // do not localize + (Key: 'bmp'; Value: 'image/bmp'), // do not localize + (Key: 'cgm'; Value: 'image/cgm'), // do not localize + (Key: 'g3'; Value: 'image/g3fax'), // do not localize + (Key: 'gif'; Value: 'image/gif'), // do not localize + (Key: 'ief'; Value: 'image/ief'), // do not localize + (Key: 'jpeg'; Value: 'image/jpeg'), // do not localize + (Key: 'jpg'; Value: 'image/jpeg'), // do not localize + (Key: 'jpe'; Value: 'image/jpeg'), // do not localize + (Key: 'ktx'; Value: 'image/ktx'), // do not localize + (Key: 'png'; Value: 'image/png'), // do not localize + (Key: 'btif'; Value: 'image/prs.btif'), // do not localize + (Key: 'sgi'; Value: 'image/sgi'), // do not localize + (Key: 'svg'; Value: 'image/svg+xml'), // do not localize + (Key: 'svgz'; Value: 'image/svg+xml'), // do not localize + (Key: 'tiff'; Value: 'image/tiff'), // do not localize + (Key: 'tif'; Value: 'image/tiff'), // do not localize + (Key: 'psd'; Value: 'image/vnd.adobe.photoshop'), // do not localize + (Key: 'uvi'; Value: 'image/vnd.dece.graphic'), // do not localize + (Key: 'uvvi'; Value: 'image/vnd.dece.graphic'), // do not localize + (Key: 'uvg'; Value: 'image/vnd.dece.graphic'), // do not localize + (Key: 'uvvg'; Value: 'image/vnd.dece.graphic'), // do not localize + (Key: 'sub'; Value: 'image/vnd.dvb.subtitle'), // do not localize + (Key: 'djvu'; Value: 'image/vnd.djvu'), // do not localize + (Key: 'djv'; Value: 'image/vnd.djvu'), // do not localize + (Key: 'dwg'; Value: 'image/vnd.dwg'), // do not localize + (Key: 'dxf'; Value: 'image/vnd.dxf'), // do not localize + (Key: 'fbs'; Value: 'image/vnd.fastbidsheet'), // do not localize + (Key: 'fpx'; Value: 'image/vnd.fpx'), // do not localize + (Key: 'fst'; Value: 'image/vnd.fst'), // do not localize + (Key: 'mmr'; Value: 'image/vnd.fujixerox.edmics-mmr'), // do not localize + (Key: 'rlc'; Value: 'image/vnd.fujixerox.edmics-rlc'), // do not localize + (Key: 'mdi'; Value: 'image/vnd.ms-modi'), // do not localize + (Key: 'wdp'; Value: 'image/vnd.ms-photo'), // do not localize + (Key: 'npx'; Value: 'image/vnd.net-fpx'), // do not localize + (Key: 'wbmp'; Value: 'image/vnd.wap.wbmp'), // do not localize + (Key: 'xif'; Value: 'image/vnd.xiff'), // do not localize + (Key: 'webp'; Value: 'image/webp'), // do not localize + (Key: '3ds'; Value: 'image/x-3ds'), // do not localize + (Key: 'ras'; Value: 'image/x-cmu-raster'), // do not localize + (Key: 'cmx'; Value: 'image/x-cmx'), // do not localize + (Key: 'fh'; Value: 'image/x-freehand'), // do not localize + (Key: 'fhc'; Value: 'image/x-freehand'), // do not localize + (Key: 'fh4'; Value: 'image/x-freehand'), // do not localize + (Key: 'fh5'; Value: 'image/x-freehand'), // do not localize + (Key: 'fh7'; Value: 'image/x-freehand'), // do not localize + (Key: 'ico'; Value: 'image/x-icon'), // do not localize + (Key: 'sid'; Value: 'image/x-mrsid-image'), // do not localize + (Key: 'pcx'; Value: 'image/x-pcx'), // do not localize + (Key: 'pic'; Value: 'image/x-pict'), // do not localize + (Key: 'pct'; Value: 'image/x-pict'), // do not localize + (Key: 'pnm'; Value: 'image/x-portable-anymap'), // do not localize + (Key: 'pbm'; Value: 'image/x-portable-bitmap'), // do not localize + (Key: 'pgm'; Value: 'image/x-portable-graymap'), // do not localize + (Key: 'ppm'; Value: 'image/x-portable-pixmap'), // do not localize + (Key: 'rgb'; Value: 'image/x-rgb'), // do not localize + (Key: 'tga'; Value: 'image/x-tga'), // do not localize + (Key: 'xbm'; Value: 'image/x-xbitmap'), // do not localize + (Key: 'xpm'; Value: 'image/x-xpixmap'), // do not localize + (Key: 'xwd'; Value: 'image/x-xwindowdump'), // do not localize + (Key: 'eml'; Value: 'message/rfc822'), // do not localize + (Key: 'mime'; Value: 'message/rfc822'), // do not localize + (Key: 'igs'; Value: 'model/iges'), // do not localize + (Key: 'iges'; Value: 'model/iges'), // do not localize + (Key: 'msh'; Value: 'model/mesh'), // do not localize + (Key: 'mesh'; Value: 'model/mesh'), // do not localize + (Key: 'silo'; Value: 'model/mesh'), // do not localize + (Key: 'dae'; Value: 'model/vnd.collada+xml'), // do not localize + (Key: 'dwf'; Value: 'model/vnd.dwf'), // do not localize + (Key: 'gdl'; Value: 'model/vnd.gdl'), // do not localize + (Key: 'gtw'; Value: 'model/vnd.gtw'), // do not localize + (Key: 'mts'; Value: 'model/vnd.mts'), // do not localize + (Key: 'vtu'; Value: 'model/vnd.vtu'), // do not localize + (Key: 'wrl'; Value: 'model/vrml'), // do not localize + (Key: 'vrml'; Value: 'model/vrml'), // do not localize + (Key: 'x3db'; Value: 'model/x3d+binary'), // do not localize + (Key: 'x3dbz'; Value: 'model/x3d+binary'), // do not localize + (Key: 'x3dv'; Value: 'model/x3d+vrml'), // do not localize + (Key: 'x3dvz'; Value: 'model/x3d+vrml'), // do not localize + (Key: 'x3d'; Value: 'model/x3d+xml'), // do not localize + (Key: 'x3dz'; Value: 'model/x3d+xml'), // do not localize + (Key: 'appcache'; Value: 'text/cache-manifest'), // do not localize + (Key: 'ics'; Value: 'text/calendar'), // do not localize + (Key: 'ifb'; Value: 'text/calendar'), // do not localize + (Key: 'css'; Value: 'text/css'), // do not localize + (Key: 'csv'; Value: 'text/csv'), // do not localize + (Key: 'html'; Value: 'text/html'), // do not localize + (Key: 'htm'; Value: 'text/html'), // do not localize + (Key: 'n3'; Value: 'text/n3'), // do not localize + (Key: 'txt'; Value: 'text/plain'), // do not localize + (Key: 'text'; Value: 'text/plain'), // do not localize + (Key: 'conf'; Value: 'text/plain'), // do not localize + (Key: 'def'; Value: 'text/plain'), // do not localize + (Key: 'list'; Value: 'text/plain'), // do not localize + (Key: 'log'; Value: 'text/plain'), // do not localize + (Key: 'in'; Value: 'text/plain'), // do not localize + (Key: 'dsc'; Value: 'text/prs.lines.tag'), // do not localize + (Key: 'rtx'; Value: 'text/richtext'), // do not localize + (Key: 'sgml'; Value: 'text/sgml'), // do not localize + (Key: 'sgm'; Value: 'text/sgml'), // do not localize + (Key: 'tsv'; Value: 'text/tab-separated-values'), // do not localize + (Key: 't'; Value: 'text/troff'), // do not localize + (Key: 'tr'; Value: 'text/troff'), // do not localize + (Key: 'roff'; Value: 'text/troff'), // do not localize + (Key: 'man'; Value: 'text/troff'), // do not localize + (Key: 'me'; Value: 'text/troff'), // do not localize + (Key: 'ms'; Value: 'text/troff'), // do not localize + (Key: 'ttl'; Value: 'text/turtle'), // do not localize + (Key: 'uri'; Value: 'text/uri-list'), // do not localize + (Key: 'uris'; Value: 'text/uri-list'), // do not localize + (Key: 'urls'; Value: 'text/uri-list'), // do not localize + (Key: 'vcard'; Value: 'text/vcard'), // do not localize + (Key: 'curl'; Value: 'text/vnd.curl'), // do not localize + (Key: 'dcurl'; Value: 'text/vnd.curl.dcurl'), // do not localize + (Key: 'scurl'; Value: 'text/vnd.curl.scurl'), // do not localize + (Key: 'mcurl'; Value: 'text/vnd.curl.mcurl'), // do not localize + (Key: 'sub'; Value: 'text/vnd.dvb.subtitle'), // do not localize + (Key: 'fly'; Value: 'text/vnd.fly'), // do not localize + (Key: 'flx'; Value: 'text/vnd.fmi.flexstor'), // do not localize + (Key: 'gv'; Value: 'text/vnd.graphviz'), // do not localize + (Key: '3dml'; Value: 'text/vnd.in3d.3dml'), // do not localize + (Key: 'spot'; Value: 'text/vnd.in3d.spot'), // do not localize + (Key: 'jad'; Value: 'text/vnd.sun.j2me.app-descriptor'), // do not localize + (Key: 'wml'; Value: 'text/vnd.wap.wml'), // do not localize + (Key: 'wmls'; Value: 'text/vnd.wap.wmlscript'), // do not localize + (Key: 's'; Value: 'text/x-asm'), // do not localize + (Key: 'asm'; Value: 'text/x-asm'), // do not localize + (Key: 'c'; Value: 'text/x-c'), // do not localize + (Key: 'cc'; Value: 'text/x-c'), // do not localize + (Key: 'cxx'; Value: 'text/x-c'), // do not localize + (Key: 'cpp'; Value: 'text/x-c'), // do not localize + (Key: 'h'; Value: 'text/x-c'), // do not localize + (Key: 'hh'; Value: 'text/x-c'), // do not localize + (Key: 'dic'; Value: 'text/x-c'), // do not localize + (Key: 'f'; Value: 'text/x-fortran'), // do not localize + (Key: 'for'; Value: 'text/x-fortran'), // do not localize + (Key: 'f77'; Value: 'text/x-fortran'), // do not localize + (Key: 'f90'; Value: 'text/x-fortran'), // do not localize + (Key: 'java'; Value: 'text/x-java-source'), // do not localize + (Key: 'opml'; Value: 'text/x-opml'), // do not localize + (Key: 'p'; Value: 'text/x-pascal'), // do not localize + (Key: 'pas'; Value: 'text/x-pascal'), // do not localize + (Key: 'nfo'; Value: 'text/x-nfo'), // do not localize + (Key: 'etx'; Value: 'text/x-setext'), // do not localize + (Key: 'sfv'; Value: 'text/x-sfv'), // do not localize + (Key: 'uu'; Value: 'text/x-uuencode'), // do not localize + (Key: 'vcs'; Value: 'text/x-vcalendar'), // do not localize + (Key: 'vcf'; Value: 'text/x-vcard'), // do not localize + (Key: '3gp'; Value: 'video/3gpp'), // do not localize + (Key: '3g2'; Value: 'video/3gpp2'), // do not localize + (Key: 'h261'; Value: 'video/h261'), // do not localize + (Key: 'h263'; Value: 'video/h263'), // do not localize + (Key: 'h264'; Value: 'video/h264'), // do not localize + (Key: 'jpgv'; Value: 'video/jpeg'), // do not localize + (Key: 'jpm'; Value: 'video/jpm'), // do not localize + (Key: 'jpgm'; Value: 'video/jpm'), // do not localize + (Key: 'mj2'; Value: 'video/mj2'), // do not localize + (Key: 'mjp2'; Value: 'video/mj2'), // do not localize + (Key: 'mp4'; Value: 'video/mp4'), // do not localize + (Key: 'mp4v'; Value: 'video/mp4'), // do not localize + (Key: 'mpg4'; Value: 'video/mp4'), // do not localize + (Key: 'mpeg'; Value: 'video/mpeg'), // do not localize + (Key: 'mpg'; Value: 'video/mpeg'), // do not localize + (Key: 'mpe'; Value: 'video/mpeg'), // do not localize + (Key: 'm1v'; Value: 'video/mpeg'), // do not localize + (Key: 'm2v'; Value: 'video/mpeg'), // do not localize + (Key: 'ogv'; Value: 'video/ogg'), // do not localize + (Key: 'qt'; Value: 'video/quicktime'), // do not localize + (Key: 'mov'; Value: 'video/quicktime'), // do not localize + (Key: 'uvh'; Value: 'video/vnd.dece.hd'), // do not localize + (Key: 'uvvh'; Value: 'video/vnd.dece.hd'), // do not localize + (Key: 'uvm'; Value: 'video/vnd.dece.mobile'), // do not localize + (Key: 'uvvm'; Value: 'video/vnd.dece.mobile'), // do not localize + (Key: 'uvp'; Value: 'video/vnd.dece.pd'), // do not localize + (Key: 'uvvp'; Value: 'video/vnd.dece.pd'), // do not localize + (Key: 'uvs'; Value: 'video/vnd.dece.sd'), // do not localize + (Key: 'uvvs'; Value: 'video/vnd.dece.sd'), // do not localize + (Key: 'uvv'; Value: 'video/vnd.dece.video'), // do not localize + (Key: 'uvvv'; Value: 'video/vnd.dece.video'), // do not localize + (Key: 'dvb'; Value: 'video/vnd.dvb.file'), // do not localize + (Key: 'fvt'; Value: 'video/vnd.fvt'), // do not localize + (Key: 'mxu'; Value: 'video/vnd.mpegurl'), // do not localize + (Key: 'm4u'; Value: 'video/vnd.mpegurl'), // do not localize + (Key: 'pyv'; Value: 'video/vnd.ms-playready.media.pyv'), // do not localize + (Key: 'uvu'; Value: 'video/vnd.uvvu.mp4'), // do not localize + (Key: 'uvvu'; Value: 'video/vnd.uvvu.mp4'), // do not localize + (Key: 'viv'; Value: 'video/vnd.vivo'), // do not localize + (Key: 'webm'; Value: 'video/webm'), // do not localize + (Key: 'f4v'; Value: 'video/x-f4v'), // do not localize + (Key: 'fli'; Value: 'video/x-fli'), // do not localize + (Key: 'flv'; Value: 'video/x-flv'), // do not localize + (Key: 'm4v'; Value: 'video/x-m4v'), // do not localize + (Key: 'mkv'; Value: 'video/x-matroska'), // do not localize + (Key: 'mk3d'; Value: 'video/x-matroska'), // do not localize + (Key: 'mks'; Value: 'video/x-matroska'), // do not localize + (Key: 'mng'; Value: 'video/x-mng'), // do not localize + (Key: 'asf'; Value: 'video/x-ms-asf'), // do not localize + (Key: 'asx'; Value: 'video/x-ms-asf'), // do not localize + (Key: 'vob'; Value: 'video/x-ms-vob'), // do not localize + (Key: 'wm'; Value: 'video/x-ms-wm'), // do not localize + (Key: 'wmv'; Value: 'video/x-ms-wmv'), // do not localize + (Key: 'wmx'; Value: 'video/x-ms-wmx'), // do not localize + (Key: 'wvx'; Value: 'video/x-ms-wvx'), // do not localize + (Key: 'avi'; Value: 'video/x-msvideo'), // do not localize + (Key: 'movie'; Value: 'video/x-sgi-movie'), // do not localize + (Key: 'smv'; Value: 'video/x-smv'), // do not localize + (Key: 'ice'; Value: 'x-conference/x-cooltalk') // do not localize + ); + {$ENDREGION} + +type + TMediaType = class + public const + DELIM_PARAMS = ';'; + CHARSET_NAME = 'charset'; + CHARSET_UTF8 = 'utf-8'; + CHARSET_UTF8_DEF = CHARSET_NAME + '=' + CHARSET_UTF8; + + TEXT_PLAIN = 'text/plain'; + TEXT_PLAIN_UTF8 = TEXT_PLAIN + DELIM_PARAMS + CHARSET_UTF8_DEF; + + TEXT_XML = 'text/xml'; + TEXT_XML_UTF8 = TEXT_XML + DELIM_PARAMS + CHARSET_UTF8_DEF; + + TEXT_HTML = 'text/html'; + TEXT_HTML_UTF8 = TEXT_HTML + DELIM_PARAMS + CHARSET_UTF8_DEF; + + APPLICATION_JSON = 'application/json'; + APPLICATION_JSON_UTF8 = APPLICATION_JSON + DELIM_PARAMS + CHARSET_UTF8_DEF; + + APPLICATION_XML = 'application/xml'; + APPLICATION_XML_UTF8 = APPLICATION_XML + DELIM_PARAMS + CHARSET_UTF8_DEF; + + APPLICATION_OCTET_STREAM = 'application/octet-stream'; + APPLICATION_FORM_URLENCODED_TYPE = 'application/x-www-form-urlencoded'; + MULTIPART_FORM_DATA = 'multipart/form-data'; + WILDCARD = '*/*'; + end; + + TCrossHttpUtils = class + private const + RFC1123_StrWeekDay: string = 'MonTueWedThuFriSatSun'; + RFC1123_StrMonth : string = 'JanFebMarAprMayJunJulAugSepOctNovDec'; + public + class function GetHttpStatusText(const AStatusCode: Integer): string; static; + class function GetFileMIMEType(const AFileName: string): string; static; + class function RFC1123_DateToStr(const ADate: TDateTime): string; static; + class function RFC1123_StrToDate(const ADateStr: string): TDateTime; static; + class function CombinePath(const APath1, APath2: string): string; static; + end; + +implementation + +{ TCrossHttpUtils } + +class function TCrossHttpUtils.GetHttpStatusText(const AStatusCode: Integer): string; +var + LItem: THttpStatus; +begin + for LItem in STATUS_CODES do + if (LItem.Code = AStatusCode) then Exit(LItem.Text); + Result := AStatusCode.ToString; +end; + +class function TCrossHttpUtils.CombinePath(const APath1, + APath2: string): string; +var + LPath1Ends, LPath2Starts: string; +begin + if (APath1 = '') then Exit(APath2); + if (APath2 = '') then Exit(APath1); + + LPath1Ends := APath1.Substring(APath1.Length - 1, 1); + LPath2Starts := APath2.Substring(0, 1); + if (LPath1Ends = '/') and (LPath2Starts = '/') then + Result := APath1 + APath2.Substring(1) + else if (LPath1Ends = '/') and (LPath2Starts <> '/') then + Result := APath1 + APath2 + else if (LPath1Ends <> '/') and (LPath2Starts = '/') then + Result := APath1 + APath2 + else + Result := APath1 + '/' + APath2; +end; + +class function TCrossHttpUtils.GetFileMIMEType(const AFileName: string): string; +var + I: Integer; + LExt: string; +begin + LExt := ExtractFileExt(AFileName).Substring(1); + for I := 0 to High(MIME_TYPES) do + if (CompareText(MIME_TYPES[I].Key, LExt) = 0) then + Exit(MIME_TYPES[I].Value); + Result := TMediaType.APPLICATION_OCTET_STREAM; +end; + +class function TCrossHttpUtils.RFC1123_DateToStr(const ADate: TDateTime): string; +var + Year, Month, Day : Word; + Hour, Min, Sec, MSec : Word; + DayOfWeek : Word; +begin + DecodeDate(ADate, Year, Month, Day); + DecodeTime(ADate, Hour, Min, Sec, MSec); + DayOfWeek := ((Trunc(aDate) - 2) mod 7); + Result := Copy(RFC1123_StrWeekDay, 1 + DayOfWeek * 3, 3) + ', ' + + Format('%2.2d %s %4.4d %2.2d:%2.2d:%2.2d GMT', + [Day, Copy(RFC1123_StrMonth, 1 + 3 * (Month - 1), 3), + Year, Hour, Min, Sec]); +end; + +class function TCrossHttpUtils.RFC1123_StrToDate(const ADateStr: string) : TDateTime; +var + Year, Month, Day : Word; + Hour, Min, Sec : Word; +begin + if (ADateStr = '') then Exit(0); + + { Fri, 30 Jul 2004 10:10:35 GMT } + Day := StrToIntDef(Copy(ADateStr, 6, 2), 0); + Month := (Pos(Copy(ADateStr, 9, 3), RFC1123_StrMonth) + 2) div 3; + Year := StrToIntDef(Copy(ADateStr, 13, 4), 0); + Hour := StrToIntDef(Copy(ADateStr, 18, 2), 0); + Min := StrToIntDef(Copy(ADateStr, 21, 2), 0); + Sec := StrToIntDef(Copy(ADateStr, 24, 2), 0); + Result := EncodeDate(Year, Month, Day); + Result := Result + EncodeTime(Hour, Min, Sec, 0); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossServer.pas b/ThirdParty/DCS/Net/Net.CrossServer.pas new file mode 100644 index 00000000..8c0b1b10 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossServer.pas @@ -0,0 +1,133 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossServer; + +interface + +uses + System.SysUtils, + Net.CrossSocket.Base, + Net.CrossSocket; + +type + ICrossServer = interface(ICrossSocket) + ['{78865955-48EE-4354-9B03-389025839B67}'] + function GetAddr: string; + function GetPort: Word; + function GetActive: Boolean; + + procedure SetAddr(const Value: string); + procedure SetPort(const Value: Word); + procedure SetActive(const Value: Boolean); + + procedure Start(const ACallback: TProc = nil); + procedure Stop; + + property Addr: string read GetAddr write SetAddr; + property Port: Word read GetPort write SetPort; + + property Active: Boolean read GetActive write SetActive; + end; + + TCrossServer = class(TCrossSocket, ICrossServer) + private + FPort: Word; + FAddr: string; + FStarted: Integer; + + function GetAddr: string; + function GetPort: Word; + function GetActive: Boolean; + + procedure SetAddr(const Value: string); + procedure SetPort(const Value: Word); + procedure SetActive(const Value: Boolean); + public + procedure Start(const ACallback: TProc = nil); + procedure Stop; + + property Addr: string read GetAddr write SetAddr; + property Port: Word read GetPort write SetPort; + property Active: Boolean read GetActive write SetActive; + end; + +implementation + +{ TCrossServer } + +function TCrossServer.GetActive: Boolean; +begin + Result := (AtomicCmpExchange(FStarted, 0, 0) = 1); +end; + +function TCrossServer.GetAddr: string; +begin + Result := FAddr; +end; + +function TCrossServer.GetPort: Word; +begin + Result := FPort; +end; + +procedure TCrossServer.SetActive(const Value: Boolean); +begin + if Value then + Start + else + Stop; +end; + +procedure TCrossServer.SetAddr(const Value: string); +begin + FAddr := Value; +end; + +procedure TCrossServer.SetPort(const Value: Word); +begin + FPort := Value; +end; + +procedure TCrossServer.Start(const ACallback: TProc); +begin + if (AtomicExchange(FStarted, 1) = 1) then + begin + if Assigned(ACallback) then + ACallback(False); + + Exit; + end; + + StartLoop; + + Listen(FAddr, FPort, + procedure(AListen: ICrossListen; ASuccess: Boolean) + begin + if not ASuccess then + AtomicExchange(FStarted, 0); + + // Ǽ˿ + // ڼɹ֮ʵʵĶ˿ȡ + if (FPort = 0) then + FPort := AListen.LocalPort; + + if Assigned(ACallback) then + ACallback(ASuccess); + end); +end; + +procedure TCrossServer.Stop; +begin + CloseAll; + StopLoop; + AtomicExchange(FStarted, 0); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSocket.Base.pas b/ThirdParty/DCS/Net/Net.CrossSocket.Base.pas new file mode 100644 index 00000000..baef176d --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSocket.Base.pas @@ -0,0 +1,1757 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSocket.Base; + +// 是否将大块数据分成小块发送(仅IOCP下有效) +// 注意: 开启该开关的情况下, 同一个连接不要在一次发送尚未结束时开始另一次发送 +// 否则会导致两块数据被分成小块后出现交错 +{.$DEFINE __LITTLE_PIECE__} + +interface + +uses + System.SysUtils, + System.Classes, + System.Math, + System.Generics.Collections, + Net.SocketAPI; + +const + // 唯一编号类别 + // 唯一编号共64位, 高2位用于表示类别 + UID_RAW = $0; + UID_LISTEN = $1; + UID_CONNECTION = $2; + + // 最大唯一编号(62个1) + UID_MASK = UInt64($3FFFFFFFFFFFFFFF); + + IPv4_ALL = '0.0.0.0'; + IPv6_ALL = '::'; + IPv4v6_ALL = ''; + IPv4_LOCAL = '127.0.0.1'; + IPv6_LOCAL = '::1'; + +type + ECrossSocket = class(Exception); + + ICrossSocket = interface; + TAbstractCrossSocket = class; + TIoEventThread = class; + + /// + /// 连接类型 + /// + TConnectType = ( + /// + /// 未知 + /// + ctUnknown, + /// + /// 由监听Accept生成的连接 + /// + ctAccept, + /// + /// 由Connect调用生成的连接 + /// + ctConnect); + + /// + /// 连接状态 + /// + TConnectStatus = ( + /// + /// 未知 + /// + csUnknown, + /// + /// 正在连接 + /// + csConnecting, + /// + /// 正在握手(SSL) + /// + csHandshaking, + /// + /// 已连接 + /// + csConnected, + /// + /// 已断开 + /// + csDisconnected, + /// + /// 已关闭 + /// + csClosed); + + /// + /// 基础数据接口 + /// + ICrossData = interface + ['{988404A3-D297-4C6D-9A76-16E50553596E}'] + function GetOwner: ICrossSocket; + function GetUID: UInt64; + function GetSocket: THandle; + function GetLocalAddr: string; + function GetLocalPort: Word; + function GetIsClosed: Boolean; + function GetUserData: Pointer; + function GetUserObject: TObject; + function GetUserInterface: IInterface; + + procedure SetUserData(const AValue: Pointer); + procedure SetUserObject(const AValue: TObject); + procedure SetUserInterface(const AValue: IInterface); + + /// + /// 更新套接字地址信息 + /// + /// + /// LocalAddr, LocalPort, PeerAddr, PeerPort 都依赖于该方法 + /// + procedure UpdateAddr; + + /// + /// 关闭套接字 + /// + procedure Close; + + /// + /// 宿主对象 + /// + property Owner: ICrossSocket read GetOwner; + + /// + /// 唯一编号 + /// + property UID: UInt64 read GetUID; + + /// + /// 套接字句柄 + /// + property Socket: THandle read GetSocket; + + /// + /// 本地IP地址 + /// + property LocalAddr: string read GetLocalAddr; + + /// + /// 本地端口 + /// + property LocalPort: Word read GetLocalPort; + + /// + /// 是否已关闭 + /// + property IsClosed: Boolean read GetIsClosed; + + /// + /// 用户数据(可以用于存储用户自定义的数据结构) + /// + property UserData: Pointer read GetUserData write SetUserData; + + /// + /// 用户数据(可以用于存储用户自定义的数据结构) + /// + property UserObject: TObject read GetUserObject write SetUserObject; + + /// + /// 用户数据(可以用于存储用户自定义的数据结构) + /// + property UserInterface: IInterface read GetUserInterface write SetUserInterface; + end; + TCrossDatas = TDictionary; + + /// + /// 监听接口 + /// + ICrossListen = interface(ICrossData) + ['{4008919E-8F16-4BBD-A68D-2FD1DE630702}'] + function GetFamily: Integer; + function GetSockType: Integer; + function GetProtocol: Integer; + + /// + /// PF_xxx + /// + property Family: Integer read GetFamily; + + /// + /// SOCK_xxx + /// + property SockType: Integer read GetSockType; + + /// + /// IPPROTO_xxx + /// + property Protocol: Integer read GetProtocol; + end; + TCrossListens = TDictionary; + + /// + /// 连接接口 + /// + ICrossConnection = interface(ICrossData) + ['{13C2A39E-C918-49B9-BBD3-A99110F94D1B}'] + function GetPeerAddr: string; + function GetPeerPort: Word; + function GetConnectType: TConnectType; + function GetConnectStatus: TConnectStatus; + + procedure SetConnectStatus(const Value: TConnectStatus); + + /// + /// 优雅关闭 + /// + procedure Disconnect; + + /// + /// 发送内存块数据 + /// + /// + /// 内存块指针 + /// + /// + /// 数据大小 + /// + /// + /// 全部数据发送完成或者出错时调用的回调函数 + /// + procedure SendBuf(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc = nil); overload; + + /// + /// 发送无类型数据 + /// + /// + /// 无类型数据 + /// + /// + /// 数据大小 + /// + /// + /// 全部数据发送完成或者出错时调用的回调函数 + /// + procedure SendBuf(const ABuffer; ACount: Integer; + const ACallback: TProc = nil); overload; + + /// + /// 发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 全部数据发送完成或者出错时调用的回调函数 + /// + procedure SendBytes(const ABytes: TBytes; AOffset, ACount: Integer; + const ACallback: TProc = nil); overload; + + /// + /// 发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 全部数据发送完成或者出错时调用的回调函数 + /// + procedure SendBytes(const ABytes: TBytes; + const ACallback: TProc = nil); overload; + + /// + /// 发送数据流(用于发送较大的数据) + /// + /// + /// 流数据 + /// + /// + /// 全部数据发送完成或者出错时调用的回调函数 + /// + /// + /// 由于是纯异步发送, 所以务必保证发送过程中 AStream 的有效性, 将 AStream 的释放放到回调函数中去
+ ///
+ procedure SendStream(const AStream: TStream; + const ACallback: TProc = nil); + + /// + /// 连接IP地址 + /// + property PeerAddr: string read GetPeerAddr; + + /// + /// 连接端口 + /// + property PeerPort: Word read GetPeerPort; + + /// + /// 连接类型 + /// + /// + /// + /// + /// ctAccept, 由监听Accept生成的连接; + /// + /// + /// ctConnect, 由Connect调用生成的连接 + /// + /// + /// + property ConnectType: TConnectType read GetConnectType; + + /// + /// 连接状态 + /// + property ConnectStatus: TConnectStatus read GetConnectStatus write SetConnectStatus; + end; + TCrossConnections = TDictionary; + + TCrossIoThreadEvent = procedure(Sender: TObject; AIoThread: TIoEventThread) of object; + TCrossListenEvent = procedure(Sender: TObject; AListen: ICrossListen) of object; + TCrossConnectEvent = procedure(Sender: TObject; AConnection: ICrossConnection) of object; + TCrossDataEvent = procedure(Sender: TObject; AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer) of object; + + /// + /// 跨平台Socket接口 + /// + ICrossSocket = interface + ['{2371CC3F-EB38-4C5D-8FA9-C913B9CD37A0}'] + function GetIoThreads: Integer; + function GetConnectionsCount: Integer; + function GetListensCount: Integer; + + function GetOnIoThreadBegin: TCrossIoThreadEvent; + function GetOnIoThreadEnd: TCrossIoThreadEvent; + function GetOnConnected: TCrossConnectEvent; + function GetOnDisconnected: TCrossConnectEvent; + function GetOnListened: TCrossListenEvent; + function GetOnListenEnd: TCrossListenEvent; + function GetOnReceived: TCrossDataEvent; + function GetOnSent: TCrossDataEvent; + + procedure SetOnIoThreadBegin(const Value: TCrossIoThreadEvent); + procedure SetOnIoThreadEnd(const Value: TCrossIoThreadEvent); + procedure SetOnConnected(const Value: TCrossConnectEvent); + procedure SetOnDisconnected(const Value: TCrossConnectEvent); + procedure SetOnListened(const Value: TCrossListenEvent); + procedure SetOnListenEnd(const Value: TCrossListenEvent); + procedure SetOnReceived(const Value: TCrossDataEvent); + procedure SetOnSent(const Value: TCrossDataEvent); + + /// + /// 启动IO循环 + /// + procedure StartLoop; + + /// + /// 停止IO循环 + /// + procedure StopLoop; + + /// + /// 处理IO事件 + /// + function ProcessIoEvent: Boolean; + + /// + /// 监听端口 + /// + /// + /// 监听地址: + /// + /// + /// 要监听IPv4和IPv6所有地址, 请设置为空 + /// + /// + /// 要单独监听IPv4, 请设置为 '0.0.0.0' + /// + /// + /// 要单独监听IPv6, 请设置为 '::' + /// + /// + /// 要监听IPv4环路地址, 请设置为 '127.0.0.1' + /// + /// + /// 要监听IPv6环路地址, 请设置为 '::1' + /// + /// + /// + /// + /// 监听端口, 设置为0则随机监听一个可用的端口 + /// + /// + /// 回调匿名函数 + /// + procedure Listen(const AHost: string; APort: Word; + const ACallback: TProc = nil); + + /// + /// 连接到主机 + /// + /// + /// 主机地址 + /// + /// + /// 主机端口 + /// + /// + /// 回调匿名函数 + /// + procedure Connect(const AHost: string; APort: Word; + const ACallback: TProc = nil); + + /// + /// 发送数据 + /// + /// + /// 连接对象 + /// + /// + /// 数据指针 + /// + /// + /// 数据尺寸 + /// + /// + /// 回调匿名函数 + /// + /// + /// 由于发送是异步的, 所以需要调用者保证发送完成之前数据的有效性 + /// + procedure Send(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer; + const ACallback: TProc = nil); + + /// + /// 关闭所有连接 + /// + /// + /// 正在发送中的数据将会丢失 + /// + procedure CloseAllConnections; + + /// + /// 关闭所有监听 + /// + procedure CloseAllListens; + + /// + /// 关闭所有监听及连接 + /// + procedure CloseAll; + + /// + /// 断开所有连接 + /// + /// + /// 正在发送中的数据会被送达 + /// + procedure DisconnectAll; + + /// + /// 加锁并返回所有连接 + /// + function LockConnections: TCrossConnections; + + /// + /// 解锁连接 + /// + procedure UnlockConnections; + + /// + /// 加锁并返回所有监听 + /// + function LockListens: TCrossListens; + + /// + /// 解锁监听 + /// + procedure UnlockListens; + + /// + /// 创建连接对象(内部使用) + /// + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; + + /// + /// 创建监听对象(内部使用) + /// + function CreateListen(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer): ICrossListen; + + {$region '物理事件'} + /// + /// 监听成功后触发(内部使用) + /// + /// + /// 监听对象 + /// + procedure TriggerListened(AListen: ICrossListen); + + /// + /// 监听结束后触发(内部使用) + /// + /// + /// 监听对象 + /// + procedure TriggerListenEnd(AListen: ICrossListen); + + /// + /// 正在连接(内部使用) + /// + /// + /// 连接对象 + /// + procedure TriggerConnecting(AConnection: ICrossConnection); + + /// + /// 连接成功后触发(内部使用) + /// + /// + /// 连接对象 + /// + procedure TriggerConnected(AConnection: ICrossConnection); + + /// + /// 连接断开后触发(内部使用) + /// + /// + /// 连接对象 + /// + procedure TriggerDisconnected(AConnection: ICrossConnection); + {$endregion} + + /// + /// IO线程开始时触发 + /// + procedure TriggerIoThreadBegin(AIoThread: TIoEventThread); + + /// + /// IO线程结束时触发 + /// + procedure TriggerIoThreadEnd(AIoThread: TIoEventThread); + + /// + /// IO线程数 + /// + property IoThreads: Integer read GetIoThreads; + + /// + /// 连接数 + /// + property ConnectionsCount: Integer read GetConnectionsCount; + + /// + /// 监听数 + /// + property ListensCount: Integer read GetListensCount; + + /// + /// IO线程开始事件 + /// + property OnIoThreadBegin: TCrossIoThreadEvent read GetOnIoThreadBegin write SetOnIoThreadBegin; + + /// + /// IO线程结束事件 + /// + property OnIoThreadEnd: TCrossIoThreadEvent read GetOnIoThreadEnd write SetOnIoThreadEnd; + + /// + /// 监听成功事件 + /// + property OnListened: TCrossListenEvent read GetOnListened write SetOnListened; + + /// + /// 监听结束事件 + /// + property OnListenEnd: TCrossListenEvent read GetOnListenEnd write SetOnListenEnd; + + /// + /// 连接成功事件 + /// + property OnConnected: TCrossConnectEvent read GetOnConnected write SetOnConnected; + + /// + /// 连接断开事件 + /// + property OnDisconnected: TCrossConnectEvent read GetOnDisconnected write SetOnDisconnected; + + /// + /// 收到数据事件 + /// + property OnReceived: TCrossDataEvent read GetOnReceived write SetOnReceived; + + /// + /// 发出数据事件 + /// + property OnSent: TCrossDataEvent read GetOnSent write SetOnSent; + end; + + TCrossData = class abstract(TInterfacedObject, ICrossData) + private + class var FCrossUID: UInt64; + private + [unsafe]FOwner: ICrossSocket; + FUID: UInt64; + FSocket: THandle; + FLocalAddr: string; + FLocalPort: Word; + FUserData: Pointer; + FUserObject: TObject; + FUserInterface: IInterface; + protected + function GetOwner: ICrossSocket; + function GetUIDTag: Byte; virtual; + function GetUID: UInt64; + function GetSocket: THandle; + function GetLocalAddr: string; + function GetLocalPort: Word; + function GetIsClosed: Boolean; virtual; abstract; + function GetUserData: Pointer; + function GetUserObject: TObject; + function GetUserInterface: IInterface; + + procedure SetUserData(const AValue: Pointer); + procedure SetUserObject(const AValue: TObject); + procedure SetUserInterface(const AValue: IInterface); + public + constructor Create(AOwner: ICrossSocket; ASocket: THandle); virtual; + destructor Destroy; override; + + procedure UpdateAddr; virtual; + procedure Close; virtual; abstract; + + property Owner: ICrossSocket read GetOwner; + property UID: UInt64 read GetUID; + property Socket: THandle read GetSocket; + property LocalAddr: string read GetLocalAddr; + property LocalPort: Word read GetLocalPort; + property IsClosed: Boolean read GetIsClosed; + property UserData: Pointer read GetUserData write SetUserData; + property UserObject: TObject read GetUserObject write SetUserObject; + property UserInterface: IInterface read GetUserInterface write SetUserInterface; + end; + + TAbstractCrossListen = class(TCrossData, ICrossListen) + private + FFamily: Integer; + FSockType: Integer; + FProtocol: Integer; + FClosed: Integer; + protected + function GetUIDTag: Byte; override; + function GetFamily: Integer; + function GetSockType: Integer; + function GetProtocol: Integer; + function GetIsClosed: Boolean; override; + public + constructor Create(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer); reintroduce; virtual; + + procedure Close; override; + + property Owner: ICrossSocket read GetOwner; + property Socket: THandle read GetSocket; + property LocalAddr: string read GetLocalAddr; + property LocalPort: Word read GetLocalPort; + property IsClosed: Boolean read GetIsClosed; + end; + + TAbstractCrossConnection = class(TCrossData, ICrossConnection) + public const + SND_BUF_SIZE = 32768; + private + FPeerAddr: string; + FPeerPort: Word; + FConnectType: TConnectType; + FConnectStatus: Integer; + protected + function GetUIDTag: Byte; override; + function GetPeerAddr: string; + function GetPeerPort: Word; + function GetConnectType: TConnectType; + function GetConnectStatus: TConnectStatus; + function GetIsClosed: Boolean; override; + + function _SetConnectStatus(const AStatus: TConnectStatus): TConnectStatus; inline; + procedure SetConnectStatus(const Value: TConnectStatus); + + procedure DirectSend(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc = nil); virtual; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); reintroduce; virtual; + + procedure UpdateAddr; override; + procedure Close; override; + procedure Disconnect; virtual; + + procedure SendBuf(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc = nil); overload; + procedure SendBuf(const ABuffer; ACount: Integer; + const ACallback: TProc = nil); overload; inline; + procedure SendBytes(const ABytes: TBytes; AOffset, ACount: Integer; + const ACallback: TProc = nil); overload; + procedure SendBytes(const ABytes: TBytes; + const ACallback: TProc = nil); overload; inline; + procedure SendStream(const AStream: TStream; + const ACallback: TProc = nil); + + property Owner: ICrossSocket read GetOwner; + property Socket: THandle read GetSocket; + property LocalAddr: string read GetLocalAddr; + property LocalPort: Word read GetLocalPort; + property IsClosed: Boolean read GetIsClosed; + + property PeerAddr: string read GetPeerAddr; + property PeerPort: Word read GetPeerPort; + property ConnectType: TConnectType read GetConnectType; + property ConnectStatus: TConnectStatus read GetConnectStatus write SetConnectStatus; + end; + + TIoEventThread = class(TThread) + private + [unsafe]FCrossSocket: ICrossSocket; + protected + procedure Execute; override; + public + constructor Create(ACrossSocket: ICrossSocket); reintroduce; + end; + + TAbstractCrossSocket = class abstract(TInterfacedObject, ICrossSocket) + protected const + RCV_BUF_SIZE = 32768; + protected class threadvar + FRecvBuf: array [0..RCV_BUF_SIZE-1] of Byte; + protected + FIoThreads: Integer; + + // 设置套接字心跳参数, 用于处理异常断线(拔网线, 主机异常掉电等造成的网络异常) + function SetKeepAlive(ASocket: THandle): Integer; + private + FConnections: TCrossConnections; + FConnectionsLock: TObject; + + FListens: TCrossListens; + FListensLock: TObject; + + FOnIoThreadBegin: TCrossIoThreadEvent; + FOnIoThreadEnd: TCrossIoThreadEvent; + FOnListened: TCrossListenEvent; + FOnListenEnd: TCrossListenEvent; + FOnConnected: TCrossConnectEvent; + FOnDisconnected: TCrossConnectEvent; + FOnReceived: TCrossDataEvent; + FOnSent: TCrossDataEvent; + + procedure _LockConnections; inline; + procedure _UnlockConnections; inline; + + procedure _LockListens; inline; + procedure _UnlockListens; inline; + + function GetConnectionsCount: Integer; + function GetListensCount: Integer; + + function GetOnIoThreadBegin: TCrossIoThreadEvent; + function GetOnIoThreadEnd: TCrossIoThreadEvent; + function GetOnConnected: TCrossConnectEvent; + function GetOnDisconnected: TCrossConnectEvent; + function GetOnListened: TCrossListenEvent; + function GetOnListenEnd: TCrossListenEvent; + function GetOnReceived: TCrossDataEvent; + function GetOnSent: TCrossDataEvent; + + procedure SetOnIoThreadBegin(const Value: TCrossIoThreadEvent); + procedure SetOnIoThreadEnd(const Value: TCrossIoThreadEvent); + procedure SetOnConnected(const Value: TCrossConnectEvent); + procedure SetOnDisconnected(const Value: TCrossConnectEvent); + procedure SetOnListened(const Value: TCrossListenEvent); + procedure SetOnListenEnd(const Value: TCrossListenEvent); + procedure SetOnReceived(const Value: TCrossDataEvent); + procedure SetOnSent(const Value: TCrossDataEvent); + protected + FConnectionsCount: Integer; + FListensCount: Integer; + + function ProcessIoEvent: Boolean; virtual; abstract; + function GetIoThreads: Integer; virtual; + + // 创建连接对象 + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; virtual; abstract; + + // 创建监听对象 + function CreateListen(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer): ICrossListen; virtual; abstract; + + {$region '物理事件'} + procedure TriggerListened(AListen: ICrossListen); virtual; + procedure TriggerListenEnd(AListen: ICrossListen); virtual; + + procedure TriggerConnecting(AConnection: ICrossConnection); virtual; + procedure TriggerConnected(AConnection: ICrossConnection); virtual; + procedure TriggerDisconnected(AConnection: ICrossConnection); virtual; + + procedure TriggerReceived(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); virtual; + procedure TriggerSent(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); virtual; + {$endregion} + + {$region '逻辑事件'} + // 这几个虚方法用于在派生类中使用 + // 比如SSL中网络端口收到的是加密数据, 可能要几次接收才会收到一个完整的 + // 已加密数据包, 然后才能解密出数据, 也就是说可能几次网络端口的接收才 + // 会对应到一次实际的数据接收, 所以设计了以下接口, 以下接口是实际数据 + // 发生时才会被触发的 + procedure LogicConnected(AConnection: ICrossConnection); virtual; + procedure LogicDisconnected(AConnection: ICrossConnection); virtual; + procedure LogicReceived(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); virtual; + procedure LogicSent(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); virtual; + {$endregion} + + procedure TriggerIoThreadBegin(AIoThread: TIoEventThread); virtual; + procedure TriggerIoThreadEnd(AIoThread: TIoEventThread); virtual; + + procedure StartLoop; virtual; abstract; + procedure StopLoop; virtual; abstract; + + procedure Listen(const AHost: string; APort: Word; + const ACallback: TProc = nil); virtual; abstract; + + procedure Connect(const AHost: string; APort: Word; + const ACallback: TProc = nil); virtual; abstract; + + procedure Send(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer; + const ACallback: TProc = nil); virtual; abstract; + + procedure CloseAllConnections; virtual; + procedure CloseAllListens; virtual; + procedure CloseAll; virtual; + procedure DisconnectAll; virtual; + public + constructor Create(AIoThreads: Integer); virtual; + destructor Destroy; override; + + procedure AfterConstruction; override; + procedure BeforeDestruction; override; + + function LockConnections: TCrossConnections; + procedure UnlockConnections; + + function LockListens: TCrossListens; + procedure UnlockListens; + + property IoThreads: Integer read GetIoThreads; + property ConnectionsCount: Integer read GetConnectionsCount; + property ListensCount: Integer read GetListensCount; + + property OnIoThreadBegin: TCrossIoThreadEvent read GetOnIoThreadBegin write SetOnIoThreadBegin; + property OnIoThreadEnd: TCrossIoThreadEvent read GetOnIoThreadEnd write SetOnIoThreadEnd; + property OnListened: TCrossListenEvent read GetOnListened write SetOnListened; + property OnListenEnd: TCrossListenEvent read GetOnListenEnd write SetOnListenEnd; + property OnConnected: TCrossConnectEvent read GetOnConnected write SetOnConnected; + property OnDisconnected: TCrossConnectEvent read GetOnDisconnected write SetOnDisconnected; + property OnReceived: TCrossDataEvent read GetOnReceived write SetOnReceived; + property OnSent: TCrossDataEvent read GetOnSent write SetOnSent; + end; + + function GetTagByUID(const AUID: UInt64): Byte; + + procedure _LogLastOsError(const ATag: string = ''); + procedure _Log(const S: string); overload; + procedure _Log(const Fmt: string; const Args: array of const); overload; + +implementation + +uses + Utils.Logger; + +function GetTagByUID(const AUID: UInt64): Byte; +begin + // 取最高 2 位 + Result := (AUID shr 62) and $03; +end; + +procedure _Log(const S: string); overload; +begin + if IsConsole then + Writeln(S) + else + AppendLog(S); +end; + +procedure _Log(const Fmt: string; const Args: array of const); overload; +begin + _Log(Format(Fmt, Args)); +end; + +procedure _LogLastOsError(const ATag: string); +{$IFDEF DEBUG} +var + LError: Integer; + LErrMsg: string; +{$ENDIF} +begin + {$IFDEF DEBUG} + LError := GetLastError; + if (ATag <> '') then + LErrMsg := ATag + ' : ' + else + LErrMsg := ''; + LErrMsg := LErrMsg + Format('System Error. Code: %0:d(%0:.4x), %1:s', + [LError, SysErrorMessage(LError)]); + _Log(LErrMsg); + {$ENDIF} +end; + +{ TIoEventThread } + +constructor TIoEventThread.Create(ACrossSocket: ICrossSocket); +begin + inherited Create(True); + FCrossSocket := ACrossSocket; + Suspended := False; +end; + +procedure TIoEventThread.Execute; +var + {$IFDEF DEBUG} + LRunCount: Int64; + {$ENDIF} + LCrossSocketObj: TAbstractCrossSocket; +begin + LCrossSocketObj := FCrossSocket as TAbstractCrossSocket; + try + LCrossSocketObj.TriggerIoThreadBegin(Self); + {$IFDEF DEBUG} + LRunCount := 0; + {$ENDIF} + while not Terminated do + begin + try + if not LCrossSocketObj.ProcessIoEvent then Break; + except + {$IFDEF DEBUG} + on e: Exception do + _Log('%s Io线程ID %d, 异常 %s, %s', [TAbstractCrossSocket(FCrossSocket).ClassName, Self.ThreadID, e.ClassName, e.Message]); + {$ENDIF} + end; + {$IFDEF DEBUG} + Inc(LRunCount) + {$ENDIF}; + end; + {$IFDEF DEBUG} + // _Log('%s Io线程ID %d, 被调用了 %d 次', [TAbstractCrossSocket(FCrossSocket).ClassName, Self.ThreadID, LRunCount]); + {$ENDIF} + finally + LCrossSocketObj.TriggerIoThreadEnd(Self); + end; +end; + +{ TAbstractCrossSocket } + +procedure TAbstractCrossSocket.CloseAll; +begin + CloseAllListens; + CloseAllConnections; +end; + +procedure TAbstractCrossSocket.CloseAllConnections; +var + LLConnectionArr: TArray; + LConnection: ICrossConnection; +begin + _LockConnections; + try + LLConnectionArr := FConnections.Values.ToArray; + finally + _UnlockConnections; + end; + + for LConnection in LLConnectionArr do + LConnection.Close; +end; + +procedure TAbstractCrossSocket.CloseAllListens; +var + LListenArr: TArray; + LListen: ICrossListen; +begin + _LockListens; + try + LListenArr := FListens.Values.ToArray; + finally + _UnlockListens; + end; + + for LListen in LListenArr do + LListen.Close; +end; + +constructor TAbstractCrossSocket.Create(AIoThreads: Integer); +begin + FIoThreads := AIoThreads; + + FListens := TCrossListens.Create; + FListensLock := TObject.Create; + + FConnections := TCrossConnections.Create; + FConnectionsLock := TObject.Create; +end; + +destructor TAbstractCrossSocket.Destroy; +begin + FreeAndNil(FListens); + FreeAndNil(FListensLock); + + FreeAndNil(FConnections); + FreeAndNil(FConnectionsLock); + + inherited; +end; + +procedure TAbstractCrossSocket.DisconnectAll; +var + LLConnectionArr: TArray; + LConnection: ICrossConnection; +begin + _LockConnections; + try + LLConnectionArr := FConnections.Values.ToArray; + finally + _UnlockConnections; + end; + + for LConnection in LLConnectionArr do + LConnection.Disconnect; +end; + +procedure TAbstractCrossSocket.AfterConstruction; +begin + StartLoop; + inherited AfterConstruction; +end; + +procedure TAbstractCrossSocket.BeforeDestruction; +begin + StopLoop; + inherited BeforeDestruction; +end; + +function TAbstractCrossSocket.GetConnectionsCount: Integer; +begin + Result := FConnectionsCount; +end; + +function TAbstractCrossSocket.GetIoThreads: Integer; +begin + if (FIoThreads > 0) then + Result := FIoThreads + else + Result := CPUCount * 2 + 1; +end; + +function TAbstractCrossSocket.GetListensCount: Integer; +begin + Result := FListensCount; +end; + +function TAbstractCrossSocket.GetOnConnected: TCrossConnectEvent; +begin + Result := FOnConnected; +end; + +function TAbstractCrossSocket.GetOnDisconnected: TCrossConnectEvent; +begin + Result := FOnDisconnected; +end; + +function TAbstractCrossSocket.GetOnIoThreadBegin: TCrossIoThreadEvent; +begin + Result := FOnIoThreadBegin; +end; + +function TAbstractCrossSocket.GetOnIoThreadEnd: TCrossIoThreadEvent; +begin + Result := FOnIoThreadEnd; +end; + +function TAbstractCrossSocket.GetOnListened: TCrossListenEvent; +begin + Result := FOnListened; +end; + +function TAbstractCrossSocket.GetOnListenEnd: TCrossListenEvent; +begin + Result := FOnListenEnd; +end; + +function TAbstractCrossSocket.GetOnReceived: TCrossDataEvent; +begin + Result := FOnReceived; +end; + +function TAbstractCrossSocket.GetOnSent: TCrossDataEvent; +begin + Result := FOnSent; +end; + +function TAbstractCrossSocket.LockConnections: TCrossConnections; +begin + _LockConnections; + Result := FConnections; +end; + +function TAbstractCrossSocket.LockListens: TCrossListens; +begin + _LockListens; + Result := FListens; +end; + +procedure TAbstractCrossSocket.LogicConnected(AConnection: ICrossConnection); +begin + +end; + +procedure TAbstractCrossSocket.LogicDisconnected(AConnection: ICrossConnection); +begin + +end; + +procedure TAbstractCrossSocket.LogicReceived(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +begin + +end; + +procedure TAbstractCrossSocket.LogicSent(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +begin + +end; + +function TAbstractCrossSocket.SetKeepAlive(ASocket: THandle): Integer; +begin + Result := TSocketAPI.SetKeepAlive(ASocket, 5, 3, 5); +end; + +procedure TAbstractCrossSocket.SetOnConnected(const Value: TCrossConnectEvent); +begin + FOnConnected := Value; +end; + +procedure TAbstractCrossSocket.SetOnDisconnected(const Value: TCrossConnectEvent); +begin + FOnDisconnected := Value; +end; + +procedure TAbstractCrossSocket.SetOnIoThreadBegin( + const Value: TCrossIoThreadEvent); +begin + FOnIoThreadBegin := Value; +end; + +procedure TAbstractCrossSocket.SetOnIoThreadEnd( + const Value: TCrossIoThreadEvent); +begin + FOnIoThreadEnd := Value; +end; + +procedure TAbstractCrossSocket.SetOnListened(const Value: TCrossListenEvent); +begin + FOnListened := Value; +end; + +procedure TAbstractCrossSocket.SetOnListenEnd(const Value: TCrossListenEvent); +begin + FOnListenEnd := Value; +end; + +procedure TAbstractCrossSocket.SetOnReceived(const Value: TCrossDataEvent); +begin + FOnReceived := Value; +end; + +procedure TAbstractCrossSocket.SetOnSent(const Value: TCrossDataEvent); +begin + FOnSent := Value; +end; + +procedure TAbstractCrossSocket.TriggerConnecting(AConnection: ICrossConnection); +begin + AConnection.ConnectStatus := csConnecting; + + _LockConnections; + try + if not FConnections.ContainsKey(AConnection.UID) then + Inc(FConnectionsCount); + + FConnections.AddOrSetValue(AConnection.UID, AConnection); + finally + _UnlockConnections; + end; +end; + +procedure TAbstractCrossSocket.TriggerConnected(AConnection: ICrossConnection); +begin + AConnection.UpdateAddr; + AConnection.ConnectStatus := csConnected; + + LogicConnected(AConnection); + + if Assigned(FOnConnected) then + FOnConnected(Self, AConnection); +end; + +procedure TAbstractCrossSocket.TriggerDisconnected(AConnection: ICrossConnection); +begin + AConnection.ConnectStatus := csClosed; + + _LockConnections; + try + if not FConnections.ContainsKey(AConnection.UID) then Exit; + + FConnections.Remove(AConnection.UID); + Dec(FConnectionsCount); + finally + _UnlockConnections; + end; + + LogicDisconnected(AConnection); + + if Assigned(FOnDisconnected) then + FOnDisconnected(Self, AConnection); +end; + +procedure TAbstractCrossSocket.TriggerIoThreadBegin(AIoThread: TIoEventThread); +begin + if Assigned(FOnIoThreadBegin) then + FOnIoThreadBegin(Self, AIoThread); +end; + +procedure TAbstractCrossSocket.TriggerIoThreadEnd(AIoThread: TIoEventThread); +begin + if Assigned(FOnIoThreadEnd) then + FOnIoThreadEnd(Self, AIoThread); +end; + +procedure TAbstractCrossSocket.TriggerListened(AListen: ICrossListen); +begin + AListen.UpdateAddr; + + _LockListens; + try + FListens.AddOrSetValue(AListen.UID, AListen); + FListensCount := FListens.Count; + finally + _UnlockListens; + end; + + if Assigned(FOnListened) then + FOnListened(Self, AListen); +end; + +procedure TAbstractCrossSocket.TriggerListenEnd(AListen: ICrossListen); +begin + _LockListens; + try + if not FListens.ContainsKey(AListen.UID) then Exit; + FListens.Remove(AListen.UID); + FListensCount := FListens.Count; + finally + _UnlockListens; + end; + + if Assigned(FOnListenEnd) then + FOnListenEnd(Self, AListen); +end; + +procedure TAbstractCrossSocket.TriggerReceived(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +begin + LogicReceived(AConnection, ABuf, ALen); + + if Assigned(FOnReceived) then + FOnReceived(Self, AConnection, ABuf, ALen); +end; + +procedure TAbstractCrossSocket.TriggerSent(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +begin + LogicSent(AConnection, ABuf, ALen); + + if Assigned(FOnSent) then + FOnSent(Self, AConnection, ABuf, ALen); +end; + +procedure TAbstractCrossSocket.UnlockConnections; +begin + _UnlockConnections; +end; + +procedure TAbstractCrossSocket.UnlockListens; +begin + _UnlockListens; +end; + +procedure TAbstractCrossSocket._LockConnections; +begin + System.TMonitor.Enter(FConnectionsLock); +end; + +procedure TAbstractCrossSocket._LockListens; +begin + System.TMonitor.Enter(FListensLock); +end; + +procedure TAbstractCrossSocket._UnlockConnections; +begin + System.TMonitor.Exit(FConnectionsLock); +end; + +procedure TAbstractCrossSocket._UnlockListens; +begin + System.TMonitor.Exit(FListensLock); +end; + +{ TCrossData } + +constructor TCrossData.Create(AOwner: ICrossSocket; ASocket: THandle); +begin + // 理论上说62位的唯一编号永远也不可能用完 + // 所以也就不用考虑编号重置的问题了 + FUID := + // 高2位 标志位 + (UInt64(GetUIDTag and $03) shl 62) or + // 低62位 编号位 + (UID_MASK and AtomicIncrement(FCrossUID)); + + FOwner := AOwner; + FSocket := ASocket; +end; + +destructor TCrossData.Destroy; +begin + if (FSocket <> INVALID_HANDLE_VALUE) then + begin + TSocketAPI.CloseSocket(FSocket); + {$IFDEF DEBUG} +// _Log('close result %d', [GetLastError]); + {$ENDIF} + FSocket := INVALID_HANDLE_VALUE; + end; + + inherited; +end; + +function TCrossData.GetLocalAddr: string; +begin + Result := FLocalAddr; +end; + +function TCrossData.GetLocalPort: Word; +begin + Result := FLocalPort; +end; + +function TCrossData.GetOwner: ICrossSocket; +begin + Result := FOwner; +end; + +function TCrossData.GetSocket: THandle; +begin + Result := FSocket; +end; + +function TCrossData.GetUID: UInt64; +begin + Result := FUID; +end; + +function TCrossData.GetUIDTag: Byte; +begin + Result := UID_RAW; +end; + +function TCrossData.GetUserData: Pointer; +begin + Result := FUserData; +end; + +function TCrossData.GetUserInterface: IInterface; +begin + Result := FUserInterface; +end; + +function TCrossData.GetUserObject: TObject; +begin + Result := FUserObject; +end; + +procedure TCrossData.SetUserData(const AValue: Pointer); +begin + FUserData := AValue; +end; + +procedure TCrossData.SetUserInterface(const AValue: IInterface); +begin + FUserInterface := AValue; +end; + +procedure TCrossData.SetUserObject(const AValue: TObject); +begin + FUserObject := AValue; +end; + +procedure TCrossData.UpdateAddr; +var + LAddr: TRawSockAddrIn; +begin + {$region '本地地址信息'} + FillChar(LAddr, SizeOf(TRawSockAddrIn), 0); + LAddr.AddrLen := SizeOf(LAddr.Addr6); + if (TSocketAPI.GetSockName(FSocket, @LAddr.Addr, LAddr.AddrLen) = 0) then + TSocketAPI.ExtractAddrInfo(@LAddr.Addr, LAddr.AddrLen, + FLocalAddr, FLocalPort); + {$endregion} +end; + +{ TAbstractCrossListen } + +constructor TAbstractCrossListen.Create(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer); +begin + inherited Create(AOwner, AListenSocket); + + FFamily := AFamily; + FSockType := ASockType; + FProtocol := AProtocol; + + FClosed := 0; +end; + +procedure TAbstractCrossListen.Close; +begin + if (AtomicExchange(FClosed, 1) = 1) then Exit; + + if (FSocket <> INVALID_HANDLE_VALUE) then + begin + TSocketAPI.CloseSocket(FSocket); + FOwner.TriggerListenEnd(Self); + FSocket := INVALID_HANDLE_VALUE; + end; +end; + +function TAbstractCrossListen.GetFamily: Integer; +begin + Result := FFamily; +end; + +function TAbstractCrossListen.GetIsClosed: Boolean; +begin + Result := (FClosed = 1); +end; + +function TAbstractCrossListen.GetProtocol: Integer; +begin + Result := FProtocol; +end; + +function TAbstractCrossListen.GetSockType: Integer; +begin + Result := FSockType; +end; + +function TAbstractCrossListen.GetUIDTag: Byte; +begin + Result := UID_LISTEN; +end; + +{ TAbstractCrossConnection } + +constructor TAbstractCrossConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +begin + inherited Create(AOwner, AClientSocket); + + FConnectType := AConnectType; +end; + +procedure TAbstractCrossConnection.SetConnectStatus(const Value: TConnectStatus); +begin + _SetConnectStatus(Value); +end; + +procedure TAbstractCrossConnection.Close; +begin + if (_SetConnectStatus(csClosed) = csClosed) then Exit; + + if (FSocket <> INVALID_HANDLE_VALUE) then + begin + TSocketAPI.CloseSocket(FSocket); + FOwner.TriggerDisconnected(Self); + FSocket := INVALID_HANDLE_VALUE; + end; +end; + +procedure TAbstractCrossConnection.DirectSend(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc); +var + LConnection: ICrossConnection; + LBuffer: Pointer; +begin + LConnection := Self as ICrossConnection; + + if (FSocket = INVALID_HANDLE_VALUE) + or IsClosed then + begin + if Assigned(ACallback) then + ACallback(LConnection, False); + Exit; + end; + + LBuffer := ABuffer; + FOwner.Send(LConnection, LBuffer, ACount, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + if ASuccess then + (FOwner as TAbstractCrossSocket).TriggerSent(AConnection, LBuffer, ACount); + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TAbstractCrossConnection.Disconnect; +begin + if (_SetConnectStatus(csDisconnected) in [csDisconnected, csClosed]) then Exit; + + TSocketAPI.Shutdown(FSocket, 2); +end; + +function TAbstractCrossConnection.GetConnectStatus: TConnectStatus; +begin + Result := TConnectStatus(AtomicCmpExchange(FConnectStatus, 0, 0)); +end; + +function TAbstractCrossConnection.GetConnectType: TConnectType; +begin + Result := FConnectType; +end; + +function TAbstractCrossConnection.GetIsClosed: Boolean; +begin + Result := (GetConnectStatus = csClosed); +end; + +function TAbstractCrossConnection.GetPeerAddr: string; +begin + Result := FPeerAddr; +end; + +function TAbstractCrossConnection.GetPeerPort: Word; +begin + Result := FPeerPort; +end; + +function TAbstractCrossConnection.GetUIDTag: Byte; +begin + Result := UID_CONNECTION; +end; + +procedure TAbstractCrossConnection.SendBuf(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc); +{$IF defined(POSIX) or not defined(__LITTLE_PIECE__)} +begin + DirectSend(ABuffer, ACount, ACallback); +end; +{$ELSE} // MSWINDOWS +// Windows下 iocp 发送数据会锁定非页面内存, 为了减少非页面内存的占用 +// 采用将大数据分小块发送的策略, 一个小块发送完之后再发送下一个 +var + LConnection: ICrossConnection; + P: PByte; + LSize: Integer; + LSender: TProc; +begin + LConnection := Self; + P := ABuffer; + LSize := ACount; + + LSender := + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + var + LData: Pointer; + LCount: Integer; + begin + if not ASuccess then + begin + LSender := nil; + + if Assigned(ACallback) then + ACallback(AConnection, False); + + AConnection.Close; + + Exit; + end; + + LData := P; + LCount := Min(LSize, SND_BUF_SIZE); + + if (LSize > LCount) then + begin + Inc(P, LCount); + Dec(LSize, LCount); + end else + begin + LSize := 0; + P := nil; + end; + + if (LData = nil) or (LCount <= 0) then + begin + LSender := nil; + + if Assigned(ACallback) then + ACallback(AConnection, True); + + Exit; + end; + + TAbstractCrossConnection(AConnection).DirectSend(LData, LCount, LSender); + end; + + LSender(LConnection, True); +end; +{$ENDIF} + +procedure TAbstractCrossConnection.SendBuf(const ABuffer; ACount: Integer; + const ACallback: TProc); +begin + SendBuf(@ABuffer, ACount, ACallback); +end; + +procedure TAbstractCrossConnection.SendBytes(const ABytes: TBytes; AOffset, + ACount: Integer; const ACallback: TProc); +var + LBytes: TBytes; +begin + // 增加引用计数 + // 由于 SendBuf 的 ABuffer 参数是直接传递的内存地址 + // 所以并不会增加 ABytes 的引用计数, 这里为了保证发送过程中数据的有效性 + // 需要定义一个局部变量用来引用 ABytes, 以维持其引用计数 + LBytes := ABytes; + SendBuf(@LBytes[AOffset], ACount, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + // 减少引用计数 + LBytes := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TAbstractCrossConnection.SendBytes(const ABytes: TBytes; + const ACallback: TProc); +begin + SendBytes(ABytes, 0, Length(ABytes), ACallback); +end; + +procedure TAbstractCrossConnection.SendStream(const AStream: TStream; + const ACallback: TProc); +var + LConnection: ICrossConnection; + LBuffer: TBytes; + LSender: TProc; +begin + if (AStream is TBytesStream) then + begin + SendBytes( + TBytesStream(AStream).Bytes, + TBytesStream(AStream).Position, + TBytesStream(AStream).Size - TBytesStream(AStream).Position, + ACallback); + Exit; + end; + + LConnection := Self; + SetLength(LBuffer, SND_BUF_SIZE); + + LSender := + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + var + LData: Pointer; + LCount: Integer; + begin + if not ASuccess then + begin + LSender := nil; + LBuffer := nil; + + if Assigned(ACallback) then + ACallback(AConnection, False); + + AConnection.Close; + + Exit; + end; + + LData := @LBuffer[0]; + LCount := AStream.Read(LBuffer[0], SND_BUF_SIZE); + + if (LData = nil) or (LCount <= 0) then + begin + LSender := nil; + LBuffer := nil; + + if Assigned(ACallback) then + ACallback(AConnection, True); + + Exit; + end; + + TAbstractCrossConnection(AConnection).DirectSend(LData, LCount, LSender); + end; + + LSender(LConnection, True); +end; + +procedure TAbstractCrossConnection.UpdateAddr; +var + LAddr: TRawSockAddrIn; +begin + inherited; + + {$region '远端地址信息'} + FillChar(LAddr, SizeOf(TRawSockAddrIn), 0); + LAddr.AddrLen := SizeOf(LAddr.Addr6); + if (TSocketAPI.GetPeerName(FSocket, @LAddr.Addr, LAddr.AddrLen) = 0) then + TSocketAPI.ExtractAddrInfo(@LAddr.Addr, LAddr.AddrLen, FPeerAddr, FPeerPort); + {$endregion} +end; + +function TAbstractCrossConnection._SetConnectStatus( + const AStatus: TConnectStatus): TConnectStatus; +begin + Result := TConnectStatus(AtomicExchange(FConnectStatus, Integer(AStatus))); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSocket.Epoll.pas b/ThirdParty/DCS/Net/Net.CrossSocket.Epoll.pas new file mode 100644 index 00000000..22e30021 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSocket.Epoll.pas @@ -0,0 +1,986 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSocket.Epoll; + +interface + +uses + System.SysUtils, + System.Classes, + System.Generics.Collections, + Posix.SysSocket, + Posix.NetinetIn, + Posix.UniStd, + Posix.NetDB, + Posix.Pthread, + Posix.Errno, + Linux.epoll, + Net.SocketAPI, + Net.CrossSocket.Base; + +type + TIoEvent = (ieRead, ieWrite); + TIoEvents = set of TIoEvent; + + TEpollListen = class(TAbstractCrossListen) + private + FLock: TObject; + FIoEvents: TIoEvents; + FOpCode: Integer; + + procedure _Lock; inline; + procedure _Unlock; inline; + + function _ReadEnabled: Boolean; inline; + function _UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; + public + constructor Create(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer); override; + destructor Destroy; override; + end; + + PSendItem = ^TSendItem; + TSendItem = record + Data: PByte; + Size: Integer; + Callback: TProc; + end; + + TSendQueue = class(TList) + protected + procedure Notify(const Value: PSendItem; Action: TCollectionNotification); override; + end; + + TEpollConnection = class(TAbstractCrossConnection) + private + FLock: TObject; + FSendQueue: TSendQueue; + FIoEvents: TIoEvents; + FConnectCallback: TProc; // 用于 Connect 回调 + FOpCode: Integer; + + procedure _Lock; inline; + procedure _Unlock; inline; + + function _ReadEnabled: Boolean; inline; + function _WriteEnabled: Boolean; inline; + function _UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); override; + destructor Destroy; override; + end; + + // KQUEUE 与 EPOLL 队列的差异 + // KQUEUE的队列中, 一个Socket句柄可以有多条记录, 每个事件一条, + // 这一点和 EPOLL 不一样, EPOLL中每个Socket句柄只会有一条记录 + // 要监测多个事件时, 只需要将多个事件做位运算加在一起调用 epoll_ctl 即可 + // + // EPOLLONESHOT 是令 epoll 支持线程池的关键 + // 该参数可以令事件触发后就立即被禁用, 避免让同一个Socket的同一个事件 + // 同时被多个工作线程触发, 由于 epoll 中每个 socket 只有一条记录, 所以 + // 一定要注意带上 EPOLLONESHOT 参数的 epoll_ctl, 在 epoll_wait 之后一定要再次 + // 调用 epoll_ctl 增加要监视的事件 + // + // EPOLL 发送数据 + // 最好的做法是将实际发送数据的动作放到 EPOLLOUT 触发时进行, 该 + // 事件触发表明 Socket 发送缓存有空闲空间了。IOCP 可以直接将待发送的数据及 + // 回调同时扔给 WSASend, 发送完成后去调用回调即可; EPOLL 机制不一样, 在 EPOLL + // 中没有类似 WSASend 的函数, 只能自行维护发送数据及回调的队列 + // EPOLL要支持多线程并发发送数据必须创建发送队列, 否则同一个 Socket 的并发发送 + // 极有可能有一部分会被其它发送覆盖掉 + // + // 由于 EPOLL 中每个套接字在队列中只有一条记录, 也就是说改写套接字的监视事件时 + // 后一次修改会修改之前的, 这就很难使用接口的引用计数机制来保持连接有效性了 + // 这里使用连接UID作为 epoll_ctl 的参数, 在事件触发时通过UID查找连接对象, 这样 + // 同样可以保证事件触发时访问到有效的连接对象, 而且不需要引用计数保证 + TEpollCrossSocket = class(TAbstractCrossSocket) + private const + MAX_EVENT_COUNT = 2048; + SHUTDOWN_FLAG = UInt64(-1); + private class threadvar + FEventList: array [0..MAX_EVENT_COUNT-1] of TEPoll_Event; + private + FEpollHandle: THandle; + FIoThreads: TArray; + FIdleHandle: THandle; + FIdleLock: TObject; + FStopHandle: THandle; + + // 利用 eventfd 唤醒并退出IO线程 + procedure _OpenStopHandle; inline; + procedure _PostStopCommand; inline; + procedure _CloseStopHandle; inline; + + procedure _OpenIdleHandle; inline; + procedure _CloseIdleHandle; inline; + + procedure _HandleAccept(AListen: ICrossListen); + procedure _HandleConnect(AConnection: ICrossConnection); + procedure _HandleRead(AConnection: ICrossConnection); + procedure _HandleWrite(AConnection: ICrossConnection); + protected + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + function CreateListen(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer): ICrossListen; override; + + procedure StartLoop; override; + procedure StopLoop; override; + + procedure Listen(const AHost: string; APort: Word; + const ACallback: TProc = nil); override; + + procedure Connect(const AHost: string; APort: Word; + const ACallback: TProc = nil); override; + + procedure Send(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer; + const ACallback: TProc = nil); override; + + function ProcessIoEvent: Boolean; override; + public + constructor Create(AIoThreads: Integer); override; + destructor Destroy; override; + end; + +implementation + +{$I Net.Posix.inc} + +{ TEpollListen } + +constructor TEpollListen.Create(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer); +begin + inherited; + + FLock := TObject.Create; + FOpCode := EPOLL_CTL_ADD; +end; + +destructor TEpollListen.Destroy; +begin + FreeAndNil(FLock); + + inherited; +end; + +procedure TEpollListen._Lock; +begin + System.TMonitor.Enter(FLock); +end; + +function TEpollListen._ReadEnabled: Boolean; +begin + Result := (ieRead in FIoEvents); +end; + +procedure TEpollListen._Unlock; +begin + System.TMonitor.Exit(FLock); +end; + +function TEpollListen._UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; +var + LOwner: TEpollCrossSocket; + LEvent: TEPoll_Event; +begin + FIoEvents := AIoEvents; + + if (FIoEvents = []) or IsClosed then Exit(False); + + LOwner := TEpollCrossSocket(Owner); + + LEvent.Events := EPOLLET or EPOLLONESHOT; + LEvent.Data.u64 := Self.UID; + + if _ReadEnabled then + LEvent.Events := LEvent.Events or EPOLLIN; + + Result := (epoll_ctl(LOwner.FEpollHandle, FOpCode, Socket, @LEvent) >= 0); + FOpCode := EPOLL_CTL_MOD; + + {$IFDEF DEBUG} + if not Result then + _Log('listen %d epoll_ctl error %d', [UID, GetLastError]); + {$ENDIF} +end; + +{ TSendQueue } + +procedure TSendQueue.Notify(const Value: PSendItem; + Action: TCollectionNotification); +begin + inherited; + + if (Action = TCollectionNotification.cnRemoved) then + begin + if (Value <> nil) then + begin + Value.Callback := nil; + FreeMem(Value, SizeOf(TSendItem)); + end; + end; +end; + +{ TEpollConnection } + +constructor TEpollConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +begin + inherited; + + FSendQueue := TSendQueue.Create; + FLock := TObject.Create; + + FOpCode := EPOLL_CTL_ADD; +end; + +destructor TEpollConnection.Destroy; +var + LConnection: ICrossConnection; + LSendItem: PSendItem; +begin + LConnection := Self; + + _Lock; + try + // 连接释放时, 调用连接回调, 告知连接失败 + // 连接成功后 FConnectCallback 会被置为 nil, + // 所以如果这里 FConnectCallback 不等于 nil, 则表示连接释放时仍未连接成功 + if Assigned(FConnectCallback) then + begin + FConnectCallback(LConnection, False); + FConnectCallback := nil; + end; + + // 连接释放时, 调用所有发送队列的回调, 告知发送失败 + if (FSendQueue.Count > 0) then + begin + for LSendItem in FSendQueue do + if Assigned(LSendItem.Callback) then + LSendItem.Callback(LConnection, False); + + FSendQueue.Clear; + end; + + FreeAndNil(FSendQueue); + finally + _Unlock; + end; + + FreeAndNil(FLock); + + inherited; +end; + +procedure TEpollConnection._Lock; +begin + System.TMonitor.Enter(FLock); +end; + +function TEpollConnection._ReadEnabled: Boolean; +begin + Result := (ieRead in FIoEvents); +end; + +procedure TEpollConnection._Unlock; +begin + System.TMonitor.Exit(FLock); +end; + +function TEpollConnection._UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; +var + LOwner: TEpollCrossSocket; + LEvent: TEPoll_Event; +begin + FIoEvents := AIoEvents; + + if (FIoEvents = []) or IsClosed then Exit(False); + + LOwner := TEpollCrossSocket(Owner); + + LEvent.Events := EPOLLET or EPOLLONESHOT; + LEvent.Data.u64 := Self.UID; + + if _ReadEnabled then + LEvent.Events := LEvent.Events or EPOLLIN; + + if _WriteEnabled then + LEvent.Events := LEvent.Events or EPOLLOUT; + + Result := (epoll_ctl(LOwner.FEpollHandle, FOpCode, Socket, @LEvent) >= 0); + FOpCode := EPOLL_CTL_MOD; + + {$IFDEF DEBUG} + if not Result then + _Log('connection %.16x epoll_ctl socket=%d events=0x%.8x error %d', + [UID, LEvent.Events, Socket, GetLastError]); + {$ENDIF} +end; + + +function TEpollConnection._WriteEnabled: Boolean; +begin + Result := (ieWrite in FIoEvents); +end; + +{ TEpollCrossSocket } + +constructor TEpollCrossSocket.Create(AIoThreads: Integer); +begin + inherited; + + FIdleLock := TObject.Create; +end; + +destructor TEpollCrossSocket.Destroy; +begin + FreeAndNil(FIdleLock); + + inherited; +end; + +procedure TEpollCrossSocket._CloseIdleHandle; +begin + FileClose(FIdleHandle); +end; + +procedure TEpollCrossSocket._CloseStopHandle; +begin + FileClose(FStopHandle); +end; + +procedure TEpollCrossSocket._HandleAccept(AListen: ICrossListen); +var + LListen: ICrossListen; + LConnection: ICrossConnection; + LEpConnection: TEpollConnection; + LSocket, LError: Integer; + LListenSocket, LClientSocket: THandle; + LSuccess: Boolean; +begin + LListen := AListen; + LListenSocket := LListen.Socket; + + while True do + begin + LSocket := TSocketAPI.Accept(LListenSocket, nil, nil); + + // Accept失败 + // EAGAIN 所有就绪的连接都已处理完毕 + // EMFILE 进程的文件句柄已经用完了 + if (LSocket < 0) then + begin + LError := GetLastError; + + // 当句柄用完了的时候, 释放事先占用的临时句柄 + // 然后再次 accept, 然后将 accept 的句柄关掉 + // 这样可以保证在文件句柄耗尽的时候依然能响应连接请求 + // 并立即将新到的连接关闭 + if (LError = EMFILE) then + begin + System.TMonitor.Enter(FIdleLock); + try + _CloseIdleHandle; + LSocket := TSocketAPI.Accept(LListenSocket, nil, nil); + TSocketAPI.CloseSocket(LSocket); + _OpenIdleHandle; + finally + System.TMonitor.Exit(FIdleLock); + end; + end; + + Break; + end; + + LClientSocket := LSocket; + TSocketAPI.SetNonBlock(LClientSocket, True); + SetKeepAlive(LClientSocket); + + LConnection := CreateConnection(Self, LClientSocket, ctAccept); + TriggerConnecting(LConnection); + TriggerConnected(LConnection); + + // 连接建立后监视Socket的读事件 + LEpConnection := LConnection as TEpollConnection; + LEpConnection._Lock; + try + LSuccess := LEpConnection._UpdateIoEvent([ieRead]); + finally + LEpConnection._Unlock; + end; + + if not LSuccess then + LConnection.Close; + end; +end; + +procedure TEpollCrossSocket._HandleConnect(AConnection: ICrossConnection); +var + LConnection: ICrossConnection; + LEpConnection: TEpollConnection; + LConnectCallback: TProc; +begin + LConnection := AConnection; + + // Connect失败 + if (TSocketAPI.GetError(LConnection.Socket) <> 0) then + begin + {$IFDEF DEBUG} + _LogLastOsError; + {$ENDIF} + LConnection.Close; + Exit; + end; + + LEpConnection := LConnection as TEpollConnection; + + LEpConnection._Lock; + try + LConnectCallback := LEpConnection.FConnectCallback; + LEpConnection.FConnectCallback := nil; + finally + LEpConnection._Unlock; + end; + + TriggerConnected(LConnection); + + if Assigned(LConnectCallback) then + LConnectCallback(LConnection, True); +end; + +procedure TEpollCrossSocket._HandleRead(AConnection: ICrossConnection); +var + LConnection: ICrossConnection; + LRcvd, LError: Integer; +begin + LConnection := AConnection; + + while True do + begin + LRcvd := TSocketAPI.Recv(LConnection.Socket, FRecvBuf[0], RCV_BUF_SIZE); + + // 对方主动断开连接 + if (LRcvd = 0) then + begin +// _Log('connection=%.16x socket=%d read 0', [LConnection.UID, LConnection.Socket]); + LConnection.Close; + Exit; + end; + + if (LRcvd < 0) then + begin + LError := GetLastError; + + // 被系统信号中断, 可以重新recv + if (LError = EINTR) then + Continue + // 接收缓冲区中数据已经被取完了 + else if (LError = EAGAIN) or (LError = EWOULDBLOCK) then + Break + else + // 接收出错 + begin +// _Log('connection=%.16x socket=%d read error %d', [LConnection.UID, LConnection.Socket, GetLastError]); + LConnection.Close; + Exit; + end; + end; + + TriggerReceived(LConnection, @FRecvBuf[0], LRcvd); + + if (LRcvd < RCV_BUF_SIZE) then Break; + end; +end; + +procedure TEpollCrossSocket._HandleWrite(AConnection: ICrossConnection); +var + LConnection: ICrossConnection; + LEpConnection: TEpollConnection; + LSendItem: PSendItem; + LCallback: TProc; + LSent: Integer; +begin + LConnection := AConnection; + LEpConnection := LConnection as TEpollConnection; + + LEpConnection._Lock; + try + // 队列中没有数据了, 清除 ioWrite 标志 + if (LEpConnection.FSendQueue.Count <= 0) then + begin + LEpConnection._UpdateIoEvent([]); + Exit; + end; + + // 获取Socket发送队列中的第一条数据 + LSendItem := LEpConnection.FSendQueue.Items[0]; + + // 发送数据 + LSent := PosixSend(LConnection.Socket, LSendItem.Data, LSendItem.Size); + + {$region '全部发送完成'} + if (LSent >= LSendItem.Size) then + begin + // 先保存回调函数, 避免后面删除队列后将其释放 + LCallback := LSendItem.Callback; + + // 发送成功, 移除已发送成功的数据 + if (LEpConnection.FSendQueue.Count > 0) then + LEpConnection.FSendQueue.Delete(0); + + // 队列中没有数据了, 清除 ioWrite 标志 + if (LEpConnection.FSendQueue.Count <= 0) then + LEpConnection._UpdateIoEvent([]); + + if Assigned(LCallback) then + LCallback(LConnection, True); + + Exit; + end; + {$endregion} + + {$region '连接断开或发送错误'} + // 发送失败的回调会在连接对象的destroy方法中被调用 + if (LSent < 0) then Exit; + {$endregion} + + {$region '部分发送成功,在下一次唤醒发送线程时继续处理剩余部分'} + Dec(LSendItem.Size, LSent); + Inc(LSendItem.Data, LSent); + {$endregion} + finally + LEpConnection._Unlock; + end; +end; + + +procedure TEpollCrossSocket._OpenIdleHandle; +begin + FIdleHandle := FileOpen('/dev/null', fmOpenRead); +end; + +procedure TEpollCrossSocket._OpenStopHandle; +var + LEvent: TEPoll_Event; +begin + FStopHandle := eventfd(0, 0); + // 这里不使用 EPOLLET + // 这样可以保证通知退出的命令发出后, 所有IO线程都会收到 + LEvent.Events := EPOLLIN; + LEvent.Data.u64 := SHUTDOWN_FLAG; + epoll_ctl(FEpollHandle, EPOLL_CTL_ADD, FStopHandle, @LEvent); +end; + +procedure TEpollCrossSocket._PostStopCommand; +var + LStuff: UInt64; +begin + LStuff := 1; + // 往 FStopHandle 写入任意数据, 唤醒工作线程 + Posix.UniStd.__write(FStopHandle, @LStuff, SizeOf(LStuff)); +end; + +procedure TEpollCrossSocket.StartLoop; +var + I: Integer; + LCrossSocket: ICrossSocket; +begin + if (FIoThreads <> nil) then Exit; + + _OpenIdleHandle; + + // epoll_create(size) + // 这个 size 只要传递大于0的任何值都可以 + // 并不是说队列的大小会受限于该值 + // http://man7.org/linux/man-pages/man2/epoll_create.2.html + FEpollHandle := epoll_create(MAX_EVENT_COUNT); + LCrossSocket := Self; + SetLength(FIoThreads, GetIoThreads); + for I := 0 to Length(FIoThreads) - 1 do + FIoThreads[I] := TIoEventThread.Create(LCrossSocket); + + _OpenStopHandle; +end; + +procedure TEpollCrossSocket.StopLoop; +var + I: Integer; + LCurrentThreadID: TThreadID; +begin + if (FIoThreads = nil) then Exit; + + CloseAll; + + while (FListensCount > 0) or (FConnectionsCount > 0) do Sleep(1); + + _PostStopCommand; + + LCurrentThreadID := GetCurrentThreadId; + for I := 0 to Length(FIoThreads) - 1 do + begin + if (FIoThreads[I].ThreadID = LCurrentThreadID) then + raise ECrossSocket.Create('不能在IO线程中执行StopLoop!'); + + FIoThreads[I].WaitFor; + FreeAndNil(FIoThreads[I]); + end; + FIoThreads := nil; + + FileClose(FEpollHandle); + _CloseIdleHandle; + _CloseStopHandle; +end; + +procedure TEpollCrossSocket.Connect(const AHost: string; APort: Word; + const ACallback: TProc); + + procedure _Failed1; + begin + if Assigned(ACallback) then + ACallback(nil, False); + end; + + function _Connect(ASocket: THandle; AAddr: PRawAddrInfo): Boolean; + var + LConnection: ICrossConnection; + LEpConnection: TEpollConnection; + begin + if (TSocketAPI.Connect(ASocket, AAddr.ai_addr, AAddr.ai_addrlen) = 0) + or (GetLastError = EINPROGRESS) then + begin + LConnection := CreateConnection(Self, ASocket, ctConnect); + TriggerConnecting(LConnection); + LEpConnection := LConnection as TEpollConnection; + + LEpConnection._Lock; + try + LEpConnection.ConnectStatus := csConnecting; + LEpConnection.FConnectCallback := ACallback; + if not LEpConnection._UpdateIoEvent([ieWrite]) then + begin + if Assigned(ACallback) then + ACallback(LConnection, False); + LConnection.Close; + Exit(False); + end; + finally + LEpConnection._Unlock; + end; + end else + begin + if Assigned(ACallback) then + ACallback(nil, False); + TSocketAPI.CloseSocket(ASocket); + Exit(False); + end; + + Result := True; + end; + +var + LHints: TRawAddrInfo; + P, LAddrInfo: PRawAddrInfo; + LSocket: THandle; +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort, LHints); + if (LAddrInfo = nil) then + begin + _Failed1; + Exit; + end; + + P := LAddrInfo; + try + while (LAddrInfo <> nil) do + begin + LSocket := TSocketAPI.NewSocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol); + if (LSocket = INVALID_HANDLE_VALUE) then + begin + _Failed1; + Exit; + end; + + TSocketAPI.SetNonBlock(LSocket, True); + SetKeepAlive(LSocket); + + if _Connect(LSocket, LAddrInfo) then Exit; + + LAddrInfo := PRawAddrInfo(LAddrInfo.ai_next); + end; + finally + TSocketAPI.FreeAddrInfo(P); + end; + + _Failed1; +end; + +function TEpollCrossSocket.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TEpollConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +function TEpollCrossSocket.CreateListen(AOwner: ICrossSocket; + AListenSocket: THandle; AFamily, ASockType, AProtocol: Integer): ICrossListen; +begin + Result := TEpollListen.Create(AOwner, AListenSocket, AFamily, ASockType, AProtocol); +end; + +procedure TEpollCrossSocket.Listen(const AHost: string; APort: Word; + const ACallback: TProc); +var + LHints: TRawAddrInfo; + P, LAddrInfo: PRawAddrInfo; + LListenSocket: THandle; + LListen: ICrossListen; + LEpListen: TEpollListen; + LSuccess: Boolean; + + procedure _Failed; + begin + if Assigned(ACallback) then + ACallback(nil, False); + end; + +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + + LHints.ai_flags := AI_PASSIVE; + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort, LHints); + if (LAddrInfo = nil) then + begin + _Failed; + Exit; + end; + + P := LAddrInfo; + try + while (LAddrInfo <> nil) do + begin + LListenSocket := TSocketAPI.NewSocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol); + if (LListenSocket = INVALID_HANDLE_VALUE) then + begin + _Failed; + Exit; + end; + + TSocketAPI.SetNonBlock(LListenSocket, True); + TSocketAPI.SetReUseAddr(LListenSocket, True); + + if (LAddrInfo.ai_family = AF_INET6) then + TSocketAPI.SetSockOpt(LListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, 1); + + if (TSocketAPI.Bind(LListenSocket, LAddrInfo.ai_addr, LAddrInfo.ai_addrlen) < 0) + or (TSocketAPI.Listen(LListenSocket) < 0) then + begin + _Failed; + Exit; + end; + + LListen := CreateListen(Self, LListenSocket, LAddrInfo.ai_family, + LAddrInfo.ai_socktype, LAddrInfo.ai_protocol); + LEpListen := LListen as TEpollListen; + + // 监听套接字的读事件 + // 读事件到达表明有新连接 + LEpListen._Lock; + try + LSuccess := LEpListen._UpdateIoEvent([ieRead]); + finally + LEpListen._Unlock; + end; + + if not LSuccess then + begin + _Failed; + + Exit; + end; + + // 监听成功 + TriggerListened(LListen); + if Assigned(ACallback) then + ACallback(LListen, True); + + // 如果端口传入0,让所有地址统一用首个分配到的端口 + if (APort = 0) and (LAddrInfo.ai_next <> nil) then + Psockaddr_in(LAddrInfo.ai_next.ai_addr).sin_port := LListen.LocalPort; + + LAddrInfo := PRawAddrInfo(LAddrInfo.ai_next); + end; + finally + TSocketAPI.FreeAddrInfo(P); + end; +end; + +procedure TEpollCrossSocket.Send(AConnection: ICrossConnection; ABuf: Pointer; + ALen: Integer; const ACallback: TProc); +var + LEpConnection: TEpollConnection; + LSendItem: PSendItem; +begin + // 测试过先发送, 然后将剩余部分放入发送队列的做法 + // 发现会引起内存访问异常, 放到队列里到IO线程中发送则不会有问题 + {$region '放入发送队列'} + GetMem(LSendItem, SizeOf(TSendItem)); + FillChar(LSendItem^, SizeOf(TSendItem), 0); + LSendItem.Data := ABuf; + LSendItem.Size := ALen; + LSendItem.Callback := ACallback; + + LEpConnection := AConnection as TEpollConnection; + + LEpConnection._Lock; + try + // 将数据放入队列 + LEpConnection.FSendQueue.Add(LSendItem); + + // 由于epoll队列中每个套接字只有一条记录, 为了避免监视发送数据的时候 + // 无法接收数据, 这里必须同时监视读和写 + if not LEpConnection._WriteEnabled then + LEpConnection._UpdateIoEvent([ieRead, ieWrite]); + finally + LEpConnection._Unlock; + end; + {$endregion} +end; + +function TEpollCrossSocket.ProcessIoEvent: Boolean; +var + LRet, I: Integer; + LEvent: TEPoll_Event; + LCrossUID: UInt64; + LCrossTag: Byte; + LListens: TCrossListens; + LConnections: TCrossConnections; + LListen: ICrossListen; + LEpListen: TEpollListen; + LConnection: ICrossConnection; + LEpConnection: TEpollConnection; + LSuccess: Boolean; + LIoEvents: TIoEvents; +begin + // 被系统信号打断或者出错会返回-1, 具体需要根据错误代码判断 + LRet := epoll_wait(FEpollHandle, @FEventList[0], MAX_EVENT_COUNT, -1); + if (LRet < 0) then + begin + LRet := GetLastError; + // EINTR, epoll_wait 调用被系统信号打断, 可以进行重试 + Exit(LRet = EINTR); + end; + + for I := 0 to LRet - 1 do + begin + LEvent := FEventList[I]; + + // 收到退出命令 + if (LEvent.Data.u64 = SHUTDOWN_FLAG) then Exit(False); + + {$region '获取连接或监听对象'} + LCrossUID := LEvent.Data.u64; + LCrossTag := GetTagByUID(LCrossUID); + LListen := nil; + LConnection := nil; + + {$IFDEF DEBUG} +// _Log('epoll events %.8x, uid %.16x, tag %d', [LEvent.Events, LEvent.Data.u64, LCrossTag]); + {$ENDIF} + case LCrossTag of + UID_LISTEN: + begin + LListens := LockListens; + try + if not LListens.TryGetValue(LCrossUID, LListen) then + Continue; + finally + UnlockListens; + end; + end; + + UID_CONNECTION: + begin + LConnections := LockConnections; + try + if not LConnections.TryGetValue(LCrossUID, LConnection) + or (LConnection = nil) then + Continue; + finally + UnlockConnections; + end; + end; + else + Continue; + end; + {$endregion} + + {$region 'IO事件处理'} + if (LListen <> nil) then + begin + if (LEvent.Events and EPOLLIN <> 0) then + _HandleAccept(LListen); + + // 继续接收新连接 + LEpListen := LListen as TEpollListen; + LEpListen._Lock; + LEpListen._UpdateIoEvent([ieRead]); + LEpListen._Unlock; + end else + if (LConnection <> nil) then + begin + // epoll的读写事件同一时间可能两个同时触发 + if (LEvent.Events and EPOLLIN <> 0) then + _HandleRead(LConnection); + + if (LEvent.Events and EPOLLOUT <> 0) then + begin + if (LConnection.ConnectStatus = csConnecting) then + _HandleConnect(LConnection) + else + _HandleWrite(LConnection); + end; + + // 把更新连接的IO事件放到这里统一处理 + // 当读写同时触发的情况, 可以节省一次IO事件更新 + if not LConnection.IsClosed then + begin + LEpConnection := LConnection as TEpollConnection; + LEpConnection._Lock; + try + if (LEpConnection.FSendQueue.Count > 0) then + LIoEvents := [ieRead, ieWrite] + else + LIoEvents := [ieRead]; + LSuccess := LEpConnection._UpdateIoEvent(LIoEvents); + finally + LEpConnection._Unlock; + end; + + if not LSuccess then + LConnection.Close; + end; + end; + {$endregion} + end; + + Result := True; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSocket.Iocp.pas b/ThirdParty/DCS/Net/Net.CrossSocket.Iocp.pas new file mode 100644 index 00000000..998c0aae --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSocket.Iocp.pas @@ -0,0 +1,765 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSocket.Iocp; + +interface + +uses + System.SysUtils, + System.Classes, + Winapi.Windows, + Net.Winsock2, + Net.Wship6, + Net.SocketAPI, + Net.CrossSocket.Base; + +type + TIocpListen = class(TAbstractCrossListen) + end; + + TIocpConnection = class(TAbstractCrossConnection) + end; + + TIocpCrossSocket = class(TAbstractCrossSocket) + private const + SHUTDOWN_FLAG = ULONG_PTR(-1); + SO_UPDATE_CONNECT_CONTEXT = $7010; + IPV6_V6ONLY = 27; + ERROR_ABANDONED_WAIT_0 = $02DF; + private type + TAddrUnion = record + case Integer of + 0: (IPv4: TSockAddrIn); + 1: (IPv6: TSockAddrIn6); + end; + + TAddrBuffer = record + Addr: TAddrUnion; + Extra: array [0..15] of Byte; + end; + + TAcceptExBuffer = array[0..SizeOf(TAddrBuffer) * 2 - 1] of Byte; + + TPerIoBufUnion = record + case Integer of + 0: (DataBuf: WSABUF); + // 这个Buffer只用于AcceptEx保存终端地址数据,大小为2倍地址结构 + 1: (AcceptExBuffer: TAcceptExBuffer); + end; + + TIocpAction = (ioAccept, ioConnect, ioRead, ioWrite); + + PPerIoData = ^TPerIoData; + TPerIoData = record + Overlapped: TWSAOverlapped; + Buffer: TPerIoBufUnion; + Action: TIocpAction; + Socket: THandle; + CrossData: ICrossData; + Callback: TProc; + end; + private + FIocpHandle: THandle; + FIoThreads: TArray; + FPerIoDataCount: NativeInt; + + function _NewIoData: PPerIoData; inline; + procedure _FreeIoData(P: PPerIoData); inline; + + procedure _NewAccept(AListen: ICrossListen); + function _NewReadZero(AConnection: ICrossConnection): Boolean; + + procedure _HandleAccept(APerIoData: PPerIoData); + procedure _HandleConnect(APerIoData: PPerIoData); + procedure _HandleRead(APerIoData: PPerIoData); + procedure _HandleWrite(APerIoData: PPerIoData); + protected + function CreateListen(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer): ICrossListen; override; + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + + procedure StartLoop; override; + procedure StopLoop; override; + + procedure Listen(const AHost: string; APort: Word; + const ACallback: TProc = nil); override; + + procedure Connect(const AHost: string; APort: Word; + const ACallback: TProc = nil); override; + + procedure Send(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer; + const ACallback: TProc = nil); override; + + function ProcessIoEvent: Boolean; override; + end; + +implementation + +{ TIocpCrossSocket } + +function TIocpCrossSocket._NewIoData: PPerIoData; +begin + GetMem(Result, SizeOf(TPerIoData)); + FillChar(Result^, SizeOf(TPerIoData), 0); + + AtomicIncrement(FPerIoDataCount); +end; + +procedure TIocpCrossSocket._FreeIoData(P: PPerIoData); +begin + if (P = nil) then Exit; + + P.CrossData := nil; + P.Callback := nil; + FreeMem(P, SizeOf(TPerIoData)); + + AtomicDecrement(FPerIoDataCount); +end; + +procedure TIocpCrossSocket._NewAccept(AListen: ICrossListen); +var + LClientSocket: THandle; + LPerIoData: PPerIoData; + LBytes: Cardinal; +begin + LClientSocket := WSASocket(AListen.Family, AListen.SockType, AListen.Protocol, + nil, 0, WSA_FLAG_OVERLAPPED); + if (LClientSocket = INVALID_SOCKET) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket._NewAccept.WSASocket'); + {$ENDIF} + Exit; + end; + + TSocketAPI.SetNonBlock(LClientSocket, True); + SetKeepAlive(LClientSocket); + + LPerIoData := _NewIoData; + LPerIoData.Action := ioAccept; + LPerIoData.Socket := LClientSocket; + LPerIoData.CrossData := AListen; + + if (not AcceptEx(AListen.Socket, LClientSocket, @LPerIoData.Buffer.AcceptExBuffer, 0, + SizeOf(TAddrBuffer), SizeOf(TAddrBuffer), LBytes, POverlapped(LPerIoData))) + and (WSAGetLastError <> WSA_IO_PENDING) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket._NewAccept.AcceptEx'); + {$ENDIF} + TSocketAPI.CloseSocket(LClientSocket); + _FreeIoData(LPerIoData); + end; +end; + +function TIocpCrossSocket._NewReadZero(AConnection: ICrossConnection): Boolean; +var + LPerIoData: PPerIoData; + LBytes, LFlags: Cardinal; +begin + LPerIoData := _NewIoData; + LPerIoData.Buffer.DataBuf.buf := nil; + LPerIoData.Buffer.DataBuf.len := 0; + LPerIoData.Action := ioRead; + LPerIoData.Socket := AConnection.Socket; + LPerIoData.CrossData := AConnection; + + LFlags := 0; + LBytes := 0; + if (WSARecv(AConnection.Socket, @LPerIoData.Buffer.DataBuf, 1, LBytes, LFlags, PWSAOverlapped(LPerIoData), nil) < 0) + and (WSAGetLastError <> WSA_IO_PENDING) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket._NewReadZero.WSARecv'); + {$ENDIF} + _FreeIoData(LPerIoData); + Exit(False); + end; + + Result := True; +end; + +procedure TIocpCrossSocket._HandleAccept(APerIoData: PPerIoData); +var + LListen: ICrossListen; + LConnection: ICrossConnection; + LClientSocket, LListenSocket: THandle; +begin + if (APerIoData.CrossData = nil) then Exit; + + LListen := APerIoData.CrossData as ICrossListen; + + _NewAccept(LListen); + + LClientSocket := APerIoData.Socket; + LListenSocket := LListen.Socket; + + // 不设置该参数, 会导致 getpeername 调用失败 + if (TSocketAPI.SetSockOpt(LClientSocket, SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, LListenSocket) < 0) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket._HandleAccept.SetSockOpt'); + {$ENDIF} + TSocketAPI.CloseSocket(LClientSocket); + Exit; + end; + + if (CreateIoCompletionPort(LClientSocket, FIocpHandle, ULONG_PTR(LClientSocket), 0) = 0) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket._HandleAccept.CreateIoCompletionPort'); + {$ENDIF} + TSocketAPI.CloseSocket(LClientSocket); + Exit; + end; + + LConnection := CreateConnection(Self, LClientSocket, ctAccept); + TriggerConnecting(LConnection); + TriggerConnected(LConnection); + + if not _NewReadZero(LConnection) then + LConnection.Close; +end; + +procedure TIocpCrossSocket._HandleConnect(APerIoData: PPerIoData); +var + LClientSocket: THandle; + LConnection: ICrossConnection; + LSuccess: Boolean; + + procedure _Failed1; + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket._HandleConnect'); + {$ENDIF} + + if Assigned(APerIoData.Callback) then + APerIoData.Callback(nil, False); + + TSocketAPI.CloseSocket(LClientSocket); + end; +begin + LClientSocket := APerIoData.Socket; + + if (TSocketAPI.GetError(LClientSocket) <> 0) then + begin + _Failed1; + Exit; + end; + + // 不设置该参数, 会导致 getpeername 调用失败 + if (TSocketAPI.SetSockOpt(LClientSocket, SOL_SOCKET, + SO_UPDATE_CONNECT_CONTEXT, 1) < 0) then + begin + _Failed1; + Exit; + end; + + LConnection := CreateConnection(Self, LClientSocket, ctConnect); + TriggerConnecting(LConnection); + + LSuccess := _NewReadZero(LConnection); + if LSuccess then + TriggerConnected(LConnection); + + if Assigned(APerIoData.Callback) then + APerIoData.Callback(LConnection, LSuccess); + + if not LSuccess then + LConnection.Close; +end; + +procedure TIocpCrossSocket._HandleRead(APerIoData: PPerIoData); +var + LConnection: ICrossConnection; + LRcvd, LError: Integer; +begin + if (APerIoData.CrossData = nil) then + begin + if Assigned(APerIoData.Callback) then + APerIoData.Callback(nil, False); + Exit; + end; + + LConnection := APerIoData.CrossData as ICrossConnection; + + while True do + begin + LRcvd := TSocketAPI.Recv(LConnection.Socket, FRecvBuf[0], RCV_BUF_SIZE); + + // 对方主动断开连接 + if (LRcvd = 0) then + begin + LConnection.Close; + Exit; + end; + + if (LRcvd < 0) then + begin + LError := GetLastError; + + // 被系统信号中断, 可以重新recv + if (LError = WSAEINTR) then + Continue + // 接收缓冲区中数据已经被取完了 + else if (LError = WSAEWOULDBLOCK) or (LError = WSAEINPROGRESS) then + Break + // 接收出错 + else + begin + LConnection.Close; + Exit; + end; + end; + + TriggerReceived(LConnection, @FRecvBuf[0], LRcvd); + + if (LRcvd < RCV_BUF_SIZE) then Break; + end; + + if not _NewReadZero(LConnection) then + LConnection.Close; +end; + +procedure TIocpCrossSocket._HandleWrite(APerIoData: PPerIoData); +begin + if Assigned(APerIoData.Callback) then + APerIoData.Callback(APerIoData.CrossData as ICrossConnection, True); +end; + +procedure TIocpCrossSocket.StartLoop; +var + I: Integer; + LCrossSocket: ICrossSocket; +begin + if (FIoThreads <> nil) then Exit; + + FIocpHandle := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); + LCrossSocket := Self; + SetLength(FIoThreads, GetIoThreads); + for I := 0 to Length(FIoThreads) - 1 do + FIoThreads[I] := TIoEventThread.Create(LCrossSocket); +end; + +procedure TIocpCrossSocket.StopLoop; + + // IO 线程在收到 SHUTDOWN_FLAG 标记之后就会退出 + // 而这时候有可能还有部分操作未完成, 其对应的 PerIoData 结构就无法释放 + // 只需要在这里再次接收完成端口的消息, 就能等到这部分未完成的操作超时或失败 + // 从而释放其对应的 PerIoData 结构 + procedure _FreeMissingPerIoDatas; + var + LBytes: Cardinal; + LSocket: THandle; + LPerIoData: PPerIoData; + LConnection: ICrossConnection; + begin + while (AtomicCmpExchange(FPerIoDataCount, 0, 0) > 0) do + begin + GetQueuedCompletionStatus(FIocpHandle, LBytes, ULONG_PTR(LSocket), POverlapped(LPerIoData), 10); + + if (LPerIoData <> nil) then + begin + if Assigned(LPerIoData.Callback) then + begin + if (LPerIoData.CrossData <> nil) + and (LPerIoData.CrossData is TIocpConnection) then + LConnection := LPerIoData.CrossData as ICrossConnection + else + LConnection := nil; + + LPerIoData.Callback(LConnection, False); + end; + + if (LPerIoData.CrossData <> nil) then + LPerIoData.CrossData.Close + else + TSocketAPI.CloseSocket(LPerIoData.Socket); + + _FreeIoData(LPerIoData); + end; + end; + end; + +var + I: Integer; + LCurrentThreadID: TThreadID; +begin + if (FIoThreads = nil) then Exit; + + CloseAll; + + while (ListensCount > 0) or (ConnectionsCount > 0) do Sleep(1); + + for I := 0 to Length(FIoThreads) - 1 do + PostQueuedCompletionStatus(FIocpHandle, 0, 0, POverlapped(SHUTDOWN_FLAG)); + + LCurrentThreadID := GetCurrentThreadId; + for I := 0 to Length(FIoThreads) - 1 do + begin + if (FIoThreads[I].ThreadID = LCurrentThreadID) then + raise ECrossSocket.Create('不能在IO线程中执行StopLoop!'); + + FIoThreads[I].WaitFor; + FreeAndNil(FIoThreads[I]); + end; + FIoThreads := nil; + + _FreeMissingPerIoDatas; + CloseHandle(FIocpHandle); +end; + +procedure TIocpCrossSocket.Connect(const AHost: string; APort: Word; + const ACallback: TProc); +var + LHints: TRawAddrInfo; + P, LAddrInfo: PRawAddrInfo; + LSocket: THandle; + + procedure _Failed1; + begin + if Assigned(ACallback) then + ACallback(nil, False); + end; + + function _Connect(ASocket: THandle; AAddr: PRawAddrInfo): Boolean; + procedure _Failed2; + begin + if Assigned(ACallback) then + ACallback(nil, False); + TSocketAPI.CloseSocket(ASocket); + end; + var + LSockAddr: TRawSockAddrIn; + LPerIoData: PPerIoData; + LBytes: Cardinal; + begin + LSockAddr.AddrLen := AAddr.ai_addrlen; + Move(AAddr.ai_addr^, LSockAddr.Addr, AAddr.ai_addrlen); + if (AAddr.ai_family = AF_INET6) then + begin + LSockAddr.Addr6.sin6_addr := in6addr_any; + LSockAddr.Addr6.sin6_port := 0; + end else + begin + LSockAddr.Addr.sin_addr.S_addr := INADDR_ANY; + LSockAddr.Addr.sin_port := 0; + end; + if (TSocketAPI.Bind(ASocket, @LSockAddr.Addr, LSockAddr.AddrLen) < 0) then + begin + _Failed2; + Exit(False); + end; + + if (CreateIoCompletionPort(ASocket, FIocpHandle, ULONG_PTR(ASocket), 0) = 0) then + begin + _Failed2; + Exit(False); + end; + + LPerIoData := _NewIoData; + LPerIoData.Action := ioConnect; + LPerIoData.Socket := ASocket; + LPerIoData.Callback := ACallback; + if not ConnectEx(ASocket, AAddr.ai_addr, AAddr.ai_addrlen, nil, 0, LBytes, PWSAOverlapped(LPerIoData)) and + (WSAGetLastError <> WSA_IO_PENDING) then + begin + _FreeIoData(LPerIoData); + _Failed2; + Exit(False); + end; + + Result := True; + end; + +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort, LHints); + if (LAddrInfo = nil) then + begin + _Failed1; + Exit; + end; + + P := LAddrInfo; + try + while (LAddrInfo <> nil) do + begin + LSocket := WSASocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol, nil, 0, WSA_FLAG_OVERLAPPED); + if (LSocket = INVALID_SOCKET) then + begin + _Failed1; + Exit; + end; + + TSocketAPI.SetNonBlock(LSocket, True); + SetKeepAlive(LSocket); + + if _Connect(LSocket, LAddrInfo) then Exit; + + LAddrInfo := PRawAddrInfo(LAddrInfo.ai_next); + end; + finally + TSocketAPI.FreeAddrInfo(P); + end; + + _Failed1; +end; + +function TIocpCrossSocket.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TIocpConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +function TIocpCrossSocket.CreateListen(AOwner: ICrossSocket; + AListenSocket: THandle; AFamily, ASockType, AProtocol: Integer): ICrossListen; +begin + Result := TIocpListen.Create(AOwner, AListenSocket, AFamily, ASockType, AProtocol); +end; + +procedure TIocpCrossSocket.Listen(const AHost: string; APort: Word; + const ACallback: TProc); +var + LHints: TRawAddrInfo; + P, LAddrInfo: PRawAddrInfo; + LListenSocket: THandle; + LListen: ICrossListen; + I: Integer; + + procedure _Failed; + begin + if Assigned(ACallback) then + ACallback(LListen, False); + + if (LListen <> nil) then + LListen.Close; + end; + + procedure _Success; + begin + TriggerListened(LListen); + + if Assigned(ACallback) then + ACallback(LListen, True); + end; +begin + LListen := nil; + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + + LHints.ai_flags := AI_PASSIVE; + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort, LHints); + if (LAddrInfo = nil) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket.Listen.GetAddrInfo'); + {$ENDIF} + _Failed; + Exit; + end; + + P := LAddrInfo; + try + while (LAddrInfo <> nil) do + begin + LListenSocket := WSASocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol, nil, 0, WSA_FLAG_OVERLAPPED); + if (LListenSocket = INVALID_SOCKET) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket.Listen.WSASocket'); + {$ENDIF} + _Failed; + Exit; + end; + + TSocketAPI.SetNonBlock(LListenSocket, True); + TSocketAPI.SetReUseAddr(LListenSocket, True); + + if (LAddrInfo.ai_family = AF_INET6) then + TSocketAPI.SetSockOpt(LListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, 1); + + if (TSocketAPI.Bind(LListenSocket, LAddrInfo.ai_addr, LAddrInfo.ai_addrlen) < 0) + or (TSocketAPI.Listen(LListenSocket) < 0) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket.Listen.Bind'); + {$ENDIF} + _Failed; + Exit; + end; + + LListen := CreateListen(Self, LListenSocket, LAddrInfo.ai_family, + LAddrInfo.ai_socktype, LAddrInfo.ai_protocol); + + if (CreateIoCompletionPort(LListenSocket, FIocpHandle, ULONG_PTR(LListenSocket), 0) = 0) then + begin + {$IFDEF DEBUG} + _LogLastOsError('TIocpCrossSocket.Listen.CreateIoCompletionPort'); + {$ENDIF} + _Failed; + Exit; + end; + + // 给每个IO线程投递一个AcceptEx + for I := 1 to GetIoThreads do + _NewAccept(LListen); + + _Success; + + // 如果端口传入0,让所有地址统一用首个分配到的端口 + if (APort = 0) and (LAddrInfo.ai_next <> nil) then + LAddrInfo.ai_next.ai_addr.sin_port := LListen.LocalPort; + + LAddrInfo := PRawAddrInfo(LAddrInfo.ai_next); + end; + finally + TSocketAPI.FreeAddrInfo(P); + end; +end; + +procedure TIocpCrossSocket.Send(AConnection: ICrossConnection; ABuf: Pointer; + ALen: Integer; const ACallback: TProc); +var + LPerIoData: PPerIoData; + LBytes, LFlags: Cardinal; +begin + LPerIoData := _NewIoData; + LPerIoData.Buffer.DataBuf.buf := ABuf; + LPerIoData.Buffer.DataBuf.len := ALen; + LPerIoData.Action := ioWrite; + LPerIoData.Socket := AConnection.Socket; + LPerIoData.CrossData := AConnection; + LPerIoData.Callback := ACallback; + + LFlags := 0; + LBytes := 0; + // WSASend 不会出现部分发送的情况, 要么全部失败, 要么全部成功 + // 所以不需要像 kqueue 或 epoll 中调用 send 那样调用完之后还得检查实际发送了多少 + // 唯一需要注意的是: WSASend 会将待发送的数据锁定到非页面内存, 非页面内存资源 + // 是非常紧张的, 所以不要无节制的调用 WSASend, 最好通过回调发送完一批数据再继 + // 续发送下一批 + if (WSASend(AConnection.Socket, @LPerIoData.Buffer.DataBuf, 1, LBytes, LFlags, PWSAOverlapped(LPerIoData), nil) < 0) + and (WSAGetLastError <> WSA_IO_PENDING) then + begin + // 出错多半是 WSAENOBUFS, 也就是投递的 WSASend 过多, 来不及发送 + // 导致非页面内存资源全部被锁定, 要避免这种情况必须上层发送逻辑 + // 保证不能无节制的调用Send发送大量数据, 最好发送完一个再继续下 + // 一个, 本函数提供了发送结果的回调函数, 在回调函数报告发送成功 + // 之后就可以继续下一块数据发送了 + _FreeIoData(LPerIoData); + + if Assigned(ACallback) then + ACallback(AConnection, False); + + AConnection.Close; + end; +end; + +function TIocpCrossSocket.ProcessIoEvent: Boolean; +var + LBytes: Cardinal; + LSocket: THandle; + LPerIoData: PPerIoData; + LConnection: ICrossConnection; + {$IFDEF DEBUG} + LErrNo: Cardinal; + {$ENDIF} +begin + if not GetQueuedCompletionStatus(FIocpHandle, LBytes, ULONG_PTR(LSocket), POverlapped(LPerIoData), INFINITE) then + begin + // 出错了, 并且完成数据也都是空的, + // 这种情况即便重试, 应该也会继续出错, 最好立即终止IO线程 + if (LPerIoData = nil) then + begin + {$IFDEF DEBUG} + LErrNo := GetLastError; + // 完成端口被关闭时可能会触发 ERROR_INVALID_HANDLE 和 ERROR_ABANDONED_WAIT_0 + if (LErrNo <> ERROR_INVALID_HANDLE) + and (LErrNo <> ERROR_ABANDONED_WAIT_0) + then + _LogLastOsError('TIocpCrossSocket.ProcessIoEvent.GetQueuedCompletionStatus'); + {$ENDIF} + Exit(False); + end; + + try + // WSA_OPERATION_ABORTED, 995, 由于线程退出或应用程序请求,已中止 I/O 操作。 + // WSAENOTSOCK, 10038, 在一个非套接字上尝试了一个操作。 + // ERROR_NETNAME_DELETED, 64, 指定的网络名不再可用 + // ERROR_CONNECTION_REFUSED, 1225, 远程计算机拒绝网络连接。 + if (LPerIoData.CrossData <> nil) then + begin + // AcceptEx虽然成功, 但是Socket句柄耗尽了, 再次投递AcceptEx + if (LPerIoData.Action = ioAccept) then + begin + // 关闭监听后会触发该错误, 这种情况不应该继续投递 + if (GetLastError <> WSA_OPERATION_ABORTED) then + _NewAccept(LPerIoData.CrossData as ICrossListen); + end else + begin + if Assigned(LPerIoData.Callback) then + begin + if (LPerIoData.CrossData is TIocpConnection) then + LConnection := LPerIoData.CrossData as ICrossConnection + else + LConnection := nil; + + LPerIoData.Callback(LConnection, False); + end; + + LPerIoData.CrossData.Close; + end; + end else + begin + if Assigned(LPerIoData.Callback) then + LPerIoData.Callback(nil, False); + + TSocketAPI.CloseSocket(LPerIoData.Socket); + end; + finally + _FreeIoData(LPerIoData); + end; + + // 出错了, 但是完成数据不是空的, 需要重试 + Exit(True); + end; + + // 主动调用了 StopLoop + if (LBytes = 0) and (ULONG_PTR(LPerIoData) = SHUTDOWN_FLAG) then Exit(False); + + // 由于未知原因未获取到完成数据, 但是返回的错误代码又是正常 + // 这种情况需要进行重试(返回True之后IO线程会再次调用ProcessIoEvent) + if (LPerIoData = nil) then Exit(True); + + try + case LPerIoData.Action of + ioAccept : _HandleAccept(LPerIoData); + ioConnect : _HandleConnect(LPerIoData); + ioRead : _HandleRead(LPerIoData); + ioWrite : _HandleWrite(LPerIoData); + end; + finally + _FreeIoData(LPerIoData); + end; + + Result := True; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSocket.Kqueue.pas b/ThirdParty/DCS/Net/Net.CrossSocket.Kqueue.pas new file mode 100644 index 00000000..447e5a08 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSocket.Kqueue.pas @@ -0,0 +1,1030 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSocket.Kqueue; + +interface + +uses + System.SysUtils, + System.Classes, + System.Generics.Collections, + Posix.SysSocket, + Posix.NetinetIn, + Posix.UniStd, + Posix.NetDB, + Posix.Pthread, + Posix.Errno, + BSD.kqueue, + Net.SocketAPI, + Net.CrossSocket.Base; + +type + TIoEvent = (ieRead, ieWrite); + TIoEvents = set of TIoEvent; + + TKqueueListen = class(TAbstractCrossListen) + private + FLock: TObject; + FIoEvents: TIoEvents; + + procedure _Lock; inline; + procedure _Unlock; inline; + + function _ReadEnabled: Boolean; inline; + function _UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; + public + constructor Create(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer); override; + destructor Destroy; override; + end; + + PSendItem = ^TSendItem; + TSendItem = record + Data: PByte; + Size: Integer; + Callback: TProc; + end; + + TSendQueue = class(TList) + protected + procedure Notify(const Value: PSendItem; Action: TCollectionNotification); override; + end; + + TKqueueConnection = class(TAbstractCrossConnection) + private + FLock: TObject; + FSendQueue: TSendQueue; + FIoEvents: TIoEvents; + FConnectCallback: TProc; // 用于 Connect 回调 + + procedure _Lock; inline; + procedure _Unlock; inline; + + function _ReadEnabled: Boolean; inline; + function _WriteEnabled: Boolean; inline; + function _UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); override; + destructor Destroy; override; + + procedure Close; override; + end; + + // KQUEUE 与 EPOLL 队列的差异 + // KQUEUE的队列中, 一个Socket句柄可以有多条记录, 每个事件一条, + // 这一点和 EPOLL 不一样, EPOLL中每个Socket句柄只会有一条记录 + // 要监测多个事件时, 只需要将多个事件做位运算加在一起调用 epoll_ctl 即可 + // + // EV_DISPATCH 和 EV_CLEAR 是令 kqueue 支持线程池的关键 + // 该参数组合可以令事件触发后就立即被禁用, 避免让同一个Socket的同一个事件 + // 同时被多个工作线程触发 + // + // EVFILT_READ + // 用于监测接收缓冲区是否可读了 + // 对于监听Socket来说,表示有新的连接到来 + // 对于已连接的Socket来说,表示有数据到达接收缓冲区 + // 为了支持线程池, 必须带上参数 EV_CLEAR or EV_DISPATCH + // 该参数组合表示, 该事件一旦触发立即清除该事件的状态并禁用它 + // 处理完连接或者读取数据之后再投递一次 EVFILT_READ, 带上参数 + // EV_ENABLE or EV_CLEAR or EV_DISPATCH, 让事件继续监测 + // + // EVFILT_WRITE + // 用于监测发送缓冲区是否可写了 + // 对于Connect中的Socket,投递EV_ENABLE,等到事件触发时表示连接已建立 + // 对于已连接的Socket,在Send之后立即投递EVFILT_WRITE,等到事件触发时表示发送完成 + // 对于EVFILT_WRITE都应该带上EV_ONESHOT参数,让该事件只会被触发一次 + // 否则,只要发送缓冲区是空的,该事件就会一直触发,这并没有什么意义 + // 我们只需要用EVFILT_WRITE去监测连接或者发送是否成功 + // + // KQUEUE 发送数据 + // 最好的做法是将实际发送数据的动作放到 EVFILT_WRITE 触发时进行, 该 + // 事件触发表明 Socket 发送缓存有空闲空间了。IOCP可以直接将待发送的数据及 + // 回调同时扔给 WSASend, 发送完成后去调用回调即可; KQUEUE 机制不一样, 在 KQUEUE + // 中没有类似 WSASend 的函数, 只能自行维护发送数据及回调的队列 + // EPOLL要支持多线程并发发送数据必须创建发送队列, 否则同一个 Socket 的并发发送 + // 极有可能有一部分会被其它发送覆盖掉 + // + // 由于 KQUEUE 中每个套接字在队列中的 EV_WRITE 和 EV_READ 是分开的两条记录 + // 所以修改套接字的监听事件时不会互相覆盖, 也就是说每个事件都会对应到一次 + // 触发, 这样就可以方便的使用接口的引用计数机制保持连接的有效性, 也不会出现 + // 内存泄漏 + TKqueueCrossSocket = class(TAbstractCrossSocket) + private const + MAX_EVENT_COUNT = 2048; + SHUTDOWN_FLAG = Pointer(-1); + private class threadvar + FEventList: array [0..MAX_EVENT_COUNT-1] of TKEvent; + private + FKqueueHandle: THandle; + FIoThreads: TArray; + FIdleHandle: THandle; + FIdleLock: TObject; + FStopHandle: TPipeDescriptors; + + // 利用 pipe 唤醒并退出IO线程 + procedure _OpenStopHandle; inline; + procedure _PostStopCommand; inline; + procedure _CloseStopHandle; inline; + + procedure _OpenIdleHandle; inline; + procedure _CloseIdleHandle; inline; + + // 在向一个已经关闭的套接字发送数据时系统会直接抛出EPIPE异常导致程序非正常退出 + // LINUX下可以在send时带上MSG_NOSIGNAL参数就能避免这种情况的发生 + // OSX中可以通过设置套接字的SO_NOSIGPIPE参数达到同样的目的 + procedure _SetNoSigPipe(ASocket: THandle); inline; + + procedure _HandleAccept(AListen: ICrossListen); + procedure _HandleConnect(AConnection: ICrossConnection); + procedure _HandleRead(AConnection: ICrossConnection); + procedure _HandleWrite(AConnection: ICrossConnection); + protected + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + function CreateListen(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer): ICrossListen; override; + + procedure StartLoop; override; + procedure StopLoop; override; + + procedure Listen(const AHost: string; APort: Word; + const ACallback: TProc = nil); override; + + procedure Connect(const AHost: string; APort: Word; + const ACallback: TProc = nil); override; + + procedure Send(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer; + const ACallback: TProc = nil); override; + + function ProcessIoEvent: Boolean; override; + public + constructor Create(AIoThreads: Integer); override; + destructor Destroy; override; + end; + +implementation + +{$I Net.Posix.inc} + +{ TKqueueListen } + +constructor TKqueueListen.Create(AOwner: ICrossSocket; AListenSocket: THandle; + AFamily, ASockType, AProtocol: Integer); +begin + inherited; + + FLock := TObject.Create; +end; + +destructor TKqueueListen.Destroy; +begin + FreeAndNil(FLock); + + inherited; +end; + +procedure TKqueueListen._Lock; +begin + System.TMonitor.Enter(FLock); +end; + +function TKqueueListen._ReadEnabled: Boolean; +begin + Result := (ieRead in FIoEvents); +end; + +procedure TKqueueListen._Unlock; +begin + System.TMonitor.Exit(FLock); +end; + +function TKqueueListen._UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; +var + LOwner: TKqueueCrossSocket; + LCrossData: Pointer; + LEvents: array [0..1] of TKEvent; + N: Integer; +begin + FIoEvents := AIoEvents; + + if (FIoEvents = []) or IsClosed then Exit(False); + + LOwner := TKqueueCrossSocket(Owner); + LCrossData := Pointer(Self); + N := 0; + + if _ReadEnabled then + begin + EV_SET(@LEvents[N], Socket, EVFILT_READ, + EV_ADD or EV_ONESHOT or EV_CLEAR or EV_DISPATCH, 0, 0, Pointer(LCrossData)); + + Inc(N); + end; + + if (N <= 0) then Exit(False); + + Result := (kevent(LOwner.FKqueueHandle, @LEvents, N, nil, 0, nil) >= 0); + + {$IFDEF DEBUG} + if not Result then + _Log('listen %d kevent error %d', [Socket, GetLastError]); + {$ENDIF} +end; + +{ TSendQueue } + +procedure TSendQueue.Notify(const Value: PSendItem; + Action: TCollectionNotification); +begin + inherited; + + if (Action = TCollectionNotification.cnRemoved) then + begin + if (Value <> nil) then + begin + Value.Callback := nil; + FreeMem(Value, SizeOf(TSendItem)); + end; + end; +end; + +{ TKqueueConnection } + +constructor TKqueueConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +begin + inherited; + + FSendQueue := TSendQueue.Create; + FLock := TObject.Create; +end; + +destructor TKqueueConnection.Destroy; +var + LConnection: ICrossConnection; + LSendItem: PSendItem; +begin + LConnection := Self; + + _Lock; + try + // 连接释放时, 调用连接回调, 告知连接失败 + // 连接成功后 FConnectCallback 会被置为 nil, + // 所以如果这里 FConnectCallback 不等于 nil, 则表示连接释放时仍未连接成功 + if Assigned(FConnectCallback) then + begin + FConnectCallback(LConnection, False); + FConnectCallback := nil; + end; + + // 连接释放时, 调用所有发送队列的回调, 告知发送失败 + if (FSendQueue.Count > 0) then + begin + for LSendItem in FSendQueue do + if Assigned(LSendItem.Callback) then + LSendItem.Callback(LConnection, False); + + FSendQueue.Clear; + end; + + FreeAndNil(FSendQueue); + finally + _Unlock; + end; + + FreeAndNil(FLock); + + inherited; +end; + +procedure TKqueueConnection.Close; +begin + if (_SetConnectStatus(csClosed) = csClosed) then Exit; + + // shutdown可以触发套接字在kqueue中的事件 + // 而直接close会将套接字从kqueue队列中移除, 不会触发任何事件 + // 这就会导致连接对象在放入kqueue队列时增加的引用计数无法回收, 导致内存泄漏 + // 使用shutdown触发事件后再释放连接可以确保不会产生内存泄露 + TSocketAPI.Shutdown(Socket, 2); +end; + +procedure TKqueueConnection._Lock; +begin + System.TMonitor.Enter(FLock); +end; + +function TKqueueConnection._ReadEnabled: Boolean; +begin + Result := (ieRead in FIoEvents); +end; + +procedure TKqueueConnection._Unlock; +begin + System.TMonitor.Exit(FLock); +end; + +function TKqueueConnection._UpdateIoEvent(const AIoEvents: TIoEvents): Boolean; +var + LOwner: TKqueueCrossSocket; + LCrossData: Pointer; + LEvents: array [0..1] of TKEvent; + N: Integer; +begin + FIoEvents := AIoEvents; + + if (FIoEvents = []) or IsClosed then Exit(False); + + LOwner := TKqueueCrossSocket(Owner); + LCrossData := Pointer(Self); + N := 0; + + // kqueue中同一个套接字的EVFILT_READ和EVFILT_WRITE事件在队列中会有两条记录 + // 并且可能会在不同的线程中同时被触发, 如果其中一个线程关闭了连接, 在没有 + // 引用计数保护的情况下, 就会导致连接对象被释放, 另一个线程再访问连接对象 + // 就会引起异常, 这里为了保证连接对象的有效性, 在添加事件时手动增加连接对象 + // 的引用计数, 到事件触发时再减少引用计数 + // 注意关闭连接一定要使用shutdown而不能直接close, 否则无法触发kqueue事件, + // 导致引用计数无法回收 + + if _ReadEnabled then + begin + Self._AddRef; + + EV_SET(@LEvents[N], Socket, EVFILT_READ, + EV_ADD or EV_ONESHOT or EV_CLEAR or EV_DISPATCH, 0, 0, Pointer(LCrossData)); + + Inc(N); + end; + + if _WriteEnabled then + begin + Self._AddRef; + + EV_SET(@LEvents[N], Socket, EVFILT_WRITE, + EV_ADD or EV_ONESHOT or EV_CLEAR or EV_DISPATCH, 0, 0, Pointer(LCrossData)); + + Inc(N); + end; + + if (N <= 0) then Exit(False); + + Result := (kevent(LOwner.FKqueueHandle, @LEvents, N, nil, 0, nil) >= 0); + + if not Result then + begin + {$IFDEF DEBUG} + _Log('connection %d kevent error %d', [Socket, GetLastError]); + {$ENDIF} + + while (N > 0) do + begin + Self._Release; + Dec(N); + end; + end; +end; + +function TKqueueConnection._WriteEnabled: Boolean; +begin + Result := (ieWrite in FIoEvents); +end; + +{ TKqueueCrossSocket } + +constructor TKqueueCrossSocket.Create(AIoThreads: Integer); +begin + inherited; + + FIdleLock := TObject.Create; +end; + +destructor TKqueueCrossSocket.Destroy; +begin + FreeAndNil(FIdleLock); + + inherited; +end; + +procedure TKqueueCrossSocket._CloseIdleHandle; +begin + FileClose(FIdleHandle); +end; + +procedure TKqueueCrossSocket._CloseStopHandle; +begin + FileClose(FStopHandle.ReadDes); + FileClose(FStopHandle.WriteDes); +end; + +procedure TKqueueCrossSocket._HandleAccept(AListen: ICrossListen); +var + LListen: ICrossListen; + LKqListen: TKqueueListen; + LConnection: ICrossConnection; + LKqConnection: TKqueueConnection; + LSocket, LError: Integer; + LListenSocket, LClientSocket: THandle; + LSuccess: Boolean; +begin + LListen := AListen; + LListenSocket := LListen.Socket; + + while True do + begin + LSocket := TSocketAPI.Accept(LListenSocket, nil, nil); + + // Accept失败 + // EAGAIN 所有就绪的连接都已处理完毕 + // EMFILE 进程的文件句柄已经用完了 + if (LSocket < 0) then + begin + LError := GetLastError; + + // 当句柄用完了的时候, 释放事先占用的临时句柄 + // 然后再次 accept, 然后将 accept 的句柄关掉 + // 这样可以保证在文件句柄耗尽的时候依然能响应连接请求 + // 并立即将新到的连接关闭 + if (LError = EMFILE) then + begin + System.TMonitor.Enter(FIdleLock); + try + _CloseIdleHandle; + LSocket := TSocketAPI.Accept(LListenSocket, nil, nil); + TSocketAPI.CloseSocket(LSocket); + _OpenIdleHandle; + finally + System.TMonitor.Exit(FIdleLock); + end; + end; + + Break; + end; + + LClientSocket := LSocket; + TSocketAPI.SetNonBlock(LClientSocket, True); + SetKeepAlive(LClientSocket); + _SetNoSigPipe(LClientSocket); + + LConnection := CreateConnection(Self, LClientSocket, ctAccept); + TriggerConnecting(LConnection); + TriggerConnected(LConnection); + + // 连接建立后监视Socket的读事件 + LKqConnection := LConnection as TKqueueConnection; + LKqConnection._Lock; + try + LSuccess := LKqConnection._UpdateIoEvent([ieRead]); + finally + LKqConnection._Unlock; + end; + + if not LSuccess then + TriggerDisconnected(LConnection); + end; + + // 继续接收新连接 + LKqListen := LListen as TKqueueListen; + LKqListen._Lock; + LKqListen._UpdateIoEvent([ieRead]); + LKqListen._Unlock; +end; + +procedure TKqueueCrossSocket._HandleConnect(AConnection: ICrossConnection); +var + LConnection: ICrossConnection; + LKqConnection: TKqueueConnection; + LConnectCallback: TProc; + LSuccess: Boolean; +begin + LConnection := AConnection; + + // Connect失败 + if (TSocketAPI.GetError(LConnection.Socket) <> 0) then + begin + {$IFDEF DEBUG} + _LogLastOsError; + {$ENDIF} + TriggerDisconnected(LConnection); + Exit; + end; + + LKqConnection := LConnection as TKqueueConnection; + + LKqConnection._Lock; + try + LConnectCallback := LKqConnection.FConnectCallback; + LKqConnection.FConnectCallback := nil; + LSuccess := LKqConnection._UpdateIoEvent([ieRead]); + finally + LKqConnection._Unlock; + end; + + if LSuccess then + TriggerConnected(LConnection); + + if Assigned(LConnectCallback) then + LConnectCallback(LConnection, LSuccess); + + if not LSuccess then + TriggerDisconnected(LConnection); +end; + +procedure TKqueueCrossSocket._HandleRead(AConnection: ICrossConnection); +var + LConnection: ICrossConnection; + LRcvd, LError: Integer; + LKqConnection: TKqueueConnection; + LSuccess: Boolean; +begin + LConnection := AConnection; + + while True do + begin + LRcvd := TSocketAPI.Recv(LConnection.Socket, FRecvBuf[0], RCV_BUF_SIZE); + + // 对方主动断开连接 + if (LRcvd = 0) then + begin +// _Log('%d close on read 0, ref %d', [LConnection.Socket, TInterfacedObject(LConnection).RefCount]); + TriggerDisconnected(LConnection); + Exit; + end; + + if (LRcvd < 0) then + begin + LError := GetLastError; + + // 被系统信号中断, 可以重新recv + if (LError = EINTR) then + Continue + // 接收缓冲区中数据已经被取完了 + else if (LError = EAGAIN) or (LError = EWOULDBLOCK) then + Break + else + // 接收出错 + begin +// _Log('%d close on read error %d, ref %d', [LConnection.Socket, GetLastError, TInterfacedObject(LConnection).RefCount]); + TriggerDisconnected(LConnection); + Exit; + end; + end; + + TriggerReceived(LConnection, @FRecvBuf[0], LRcvd); + + if (LRcvd < RCV_BUF_SIZE) then Break; + end; + + LKqConnection := LConnection as TKqueueConnection; + LKqConnection._Lock; + try + LSuccess := LKqConnection._UpdateIoEvent([ieRead]); + finally + LKqConnection._Unlock; + end; + + if not LSuccess then + TriggerDisconnected(LConnection); +end; + +procedure TKqueueCrossSocket._HandleWrite(AConnection: ICrossConnection); +var + LConnection: ICrossConnection; + LKqConnection: TKqueueConnection; + LSendItem: PSendItem; + LCallback: TProc; + LSent: Integer; +begin + LConnection := AConnection; + LKqConnection := LConnection as TKqueueConnection; + + LKqConnection._Lock; + try + // 队列中没有数据了, 清除 ioWrite 标志 + if (LKqConnection.FSendQueue.Count <= 0) then + begin + LKqConnection._UpdateIoEvent([]); + Exit; + end; + + // 获取Socket发送队列中的第一条数据 + LSendItem := LKqConnection.FSendQueue.Items[0]; + + // 发送数据 + LSent := PosixSend(LConnection.Socket, LSendItem.Data, LSendItem.Size); + + {$region '全部发送完成'} + if (LSent >= LSendItem.Size) then + begin + // 先保存回调函数, 避免后面删除队列后将其释放 + LCallback := LSendItem.Callback; + + // 发送成功, 移除已发送成功的数据 + if (LKqConnection.FSendQueue.Count > 0) then + LKqConnection.FSendQueue.Delete(0); + + // 队列中没有数据了, 清除 ioWrite 标志 + if (LKqConnection.FSendQueue.Count <= 0) then + LKqConnection._UpdateIoEvent([]); + + if Assigned(LCallback) then + LCallback(LConnection, True); + + Exit; + end; + {$endregion} + + {$region '连接断开或发送错误'} + // 发送失败的回调会在连接对象的destroy方法中被调用 + if (LSent < 0) then Exit; + {$endregion} + + {$region '部分发送成功,在下一次唤醒发送线程时继续处理剩余部分'} + Dec(LSendItem.Size, LSent); + Inc(LSendItem.Data, LSent); + {$endregion} + + LKqConnection._UpdateIoEvent([ieWrite]); + finally + LKqConnection._Unlock; + end; +end; + + +procedure TKqueueCrossSocket._OpenIdleHandle; +begin + FIdleHandle := FileOpen('/dev/null', fmOpenRead); +end; + +procedure TKqueueCrossSocket._OpenStopHandle; +var + LEvent: TKEvent; +begin + pipe(FStopHandle); + + // 这里不使用 EV_ONESHOT + // 这样可以保证通知退出的命令发出后, 所有IO线程都会收到 + EV_SET(@LEvent, FStopHandle.ReadDes, EVFILT_READ, + EV_ADD, 0, 0, SHUTDOWN_FLAG); + kevent(FKqueueHandle, @LEvent, 1, nil, 0, nil); +end; + +procedure TKqueueCrossSocket._PostStopCommand; +var + LStuff: UInt64; +begin + LStuff := 1; + // 往 FStopHandle.WriteDes 写入任意数据, 唤醒工作线程 + Posix.UniStd.__write(FStopHandle.WriteDes, @LStuff, SizeOf(LStuff)); +end; + +procedure TKqueueCrossSocket._SetNoSigPipe(ASocket: THandle); +begin + TSocketAPI.SetSockOpt(ASocket, SOL_SOCKET, SO_NOSIGPIPE, 1); +end; + +procedure TKqueueCrossSocket.StartLoop; +var + I: Integer; + LCrossSocket: ICrossSocket; +begin + if (FIoThreads <> nil) then Exit; + + _OpenIdleHandle; + + FKqueueHandle := kqueue(); + LCrossSocket := Self; + SetLength(FIoThreads, GetIoThreads); + for I := 0 to Length(FIoThreads) - 1 do + FIoThreads[i] := TIoEventThread.Create(LCrossSocket); + + _OpenStopHandle; +end; + +procedure TKqueueCrossSocket.StopLoop; +var + I: Integer; + LCurrentThreadID: TThreadID; +begin + if (FIoThreads = nil) then Exit; + + CloseAll; + + while (ListensCount > 0) or (ConnectionsCount > 0) do Sleep(1); + + _PostStopCommand; + + LCurrentThreadID := GetCurrentThreadId; + for I := 0 to Length(FIoThreads) - 1 do + begin + if (FIoThreads[I].ThreadID = LCurrentThreadID) then + raise ECrossSocket.Create('不能在IO线程中执行StopLoop!'); + + FIoThreads[I].WaitFor; + FreeAndNil(FIoThreads[I]); + end; + FIoThreads := nil; + + FileClose(FKqueueHandle); + _CloseIdleHandle; + _CloseStopHandle; +end; + +procedure TKqueueCrossSocket.Connect(const AHost: string; APort: Word; + const ACallback: TProc); + + procedure _Failed1; + begin + if Assigned(ACallback) then + ACallback(nil, False); + end; + + function _Connect(ASocket: THandle; AAddr: PRawAddrInfo): Boolean; + var + LConnection: ICrossConnection; + LKqConnection: TKqueueConnection; + begin + if (TSocketAPI.Connect(ASocket, AAddr.ai_addr, AAddr.ai_addrlen) = 0) + or (GetLastError = EINPROGRESS) then + begin + LConnection := CreateConnection(Self, ASocket, ctConnect); + TriggerConnecting(LConnection); + LKqConnection := LConnection as TKqueueConnection; + + LKqConnection._Lock; + try + LKqConnection.ConnectStatus := csConnecting; + LKqConnection.FConnectCallback := ACallback; + if not LKqConnection._UpdateIoEvent([ieWrite]) then + begin + TriggerDisconnected(LConnection); + if Assigned(ACallback) then + ACallback(LConnection, False); + Exit(False); + end; + finally + LKqConnection._Unlock; + end; + end else + begin + if Assigned(ACallback) then + ACallback(nil, False); + TSocketAPI.CloseSocket(ASocket); + Exit(False); + end; + + Result := True; + end; + +var + LHints: TRawAddrInfo; + P, LAddrInfo: PRawAddrInfo; + LSocket: THandle; +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort, LHints); + if (LAddrInfo = nil) then + begin + _Failed1; + Exit; + end; + + P := LAddrInfo; + try + while (LAddrInfo <> nil) do + begin + LSocket := TSocketAPI.NewSocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol); + if (LSocket = INVALID_HANDLE_VALUE) then + begin + _Failed1; + Exit; + end; + + TSocketAPI.SetNonBlock(LSocket, True); + SetKeepAlive(LSocket); + _SetNoSigPipe(LSocket); + + if _Connect(LSocket, LAddrInfo) then Exit; + + LAddrInfo := PRawAddrInfo(LAddrInfo.ai_next); + end; + finally + TSocketAPI.FreeAddrInfo(P); + end; + + _Failed1; +end; + +function TKqueueCrossSocket.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TKqueueConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +function TKqueueCrossSocket.CreateListen(AOwner: ICrossSocket; + AListenSocket: THandle; AFamily, ASockType, AProtocol: Integer): ICrossListen; +begin + Result := TKqueueListen.Create(AOwner, AListenSocket, AFamily, ASockType, AProtocol); +end; + +procedure TKqueueCrossSocket.Listen(const AHost: string; APort: Word; + const ACallback: TProc); +var + LHints: TRawAddrInfo; + P, LAddrInfo: PRawAddrInfo; + LListenSocket: THandle; + LListen: ICrossListen; + LKqListen: TKqueueListen; + LSuccess: Boolean; + + procedure _Failed; + begin + if Assigned(ACallback) then + ACallback(nil, False); + end; + +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + + LHints.ai_flags := AI_PASSIVE; + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort, LHints); + if (LAddrInfo = nil) then + begin + _Failed; + Exit; + end; + + P := LAddrInfo; + try + while (LAddrInfo <> nil) do + begin + LListenSocket := TSocketAPI.NewSocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol); + if (LListenSocket = INVALID_HANDLE_VALUE) then + begin + _Failed; + Exit; + end; + + TSocketAPI.SetNonBlock(LListenSocket, True); + TSocketAPI.SetReUseAddr(LListenSocket, True); + + if (LAddrInfo.ai_family = AF_INET6) then + TSocketAPI.SetSockOpt(LListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, 1); + + if (TSocketAPI.Bind(LListenSocket, LAddrInfo.ai_addr, LAddrInfo.ai_addrlen) < 0) + or (TSocketAPI.Listen(LListenSocket) < 0) then + begin + _Failed; + Exit; + end; + + LListen := CreateListen(Self, LListenSocket, LAddrInfo.ai_family, + LAddrInfo.ai_socktype, LAddrInfo.ai_protocol); + LKqListen := LListen as TKqueueListen; + + // 监听套接字的读事件 + // 读事件到达表明有新连接 + LKqListen._Lock; + try + LSuccess := LKqListen._UpdateIoEvent([ieRead]); + finally + LKqListen._Unlock; + end; + + if not LSuccess then + begin + _Failed; + + Exit; + end; + + // 监听成功 + TriggerListened(LListen); + if Assigned(ACallback) then + ACallback(LListen, True); + + // 如果端口传入0,让所有地址统一用首个分配到的端口 + if (APort = 0) and (LAddrInfo.ai_next <> nil) then + Psockaddr_in(LAddrInfo.ai_next.ai_addr).sin_port := LListen.LocalPort; + + LAddrInfo := PRawAddrInfo(LAddrInfo.ai_next); + end; + finally + TSocketAPI.FreeAddrInfo(P); + end; +end; + +procedure TKqueueCrossSocket.Send(AConnection: ICrossConnection; ABuf: Pointer; + ALen: Integer; const ACallback: TProc); +var + LKqConnection: TKqueueConnection; + LSendItem: PSendItem; +begin + // 测试过先发送, 然后将剩余部分放入发送队列的做法 + // 发现会引起内存访问异常, 放到队列里到IO线程中发送则不会有问题 + {$region '放入发送队列'} + GetMem(LSendItem, SizeOf(TSendItem)); + FillChar(LSendItem^, SizeOf(TSendItem), 0); + LSendItem.Data := ABuf; + LSendItem.Size := ALen; + LSendItem.Callback := ACallback; + + LKqConnection := AConnection as TKqueueConnection; + + LKqConnection._Lock; + try + // 将数据放入队列 + LKqConnection.FSendQueue.Add(LSendItem); + + // 由于 kqueue 队列中每个套接字的读写事件是分开的两条记录 + // 所以发送只需要添加写事件即可, 不用管读事件, 否则反而会引起引用计数异常 + if not LKqConnection._WriteEnabled then + LKqConnection._UpdateIoEvent([ieWrite]); + finally + LKqConnection._Unlock; + end; + {$endregion} +end; + +function TKqueueCrossSocket.ProcessIoEvent: Boolean; +var + LRet, I: Integer; + LEvent: TKEvent; + LCrossData: TCrossData; + LListen: ICrossListen; + LConnection: ICrossConnection; +begin + LRet := kevent(FKqueueHandle, nil, 0, @FEventList[0], MAX_EVENT_COUNT, nil); + if (LRet < 0) then + begin + LRet := GetLastError; + // EINTR, kevent 调用被系统信号打断, 可以进行重试 + Exit(LRet = EINTR); + end; + + for I := 0 to LRet - 1 do + begin + LEvent := FEventList[I]; + + // 收到退出命令 + if (LEvent.uData = SHUTDOWN_FLAG) then Exit(False); + + if (LEvent.uData = nil) then Continue; + + {$region '获取连接或监听对象'} + LCrossData := TCrossData(LEvent.uData); + + if (LCrossData is TKqueueListen) then + LListen := LCrossData as ICrossListen + else + LListen := nil; + + if (LCrossData is TKqueueConnection) then + LConnection := LCrossData as ICrossConnection + else + LConnection := nil; + {$endregion} + + {$region 'IO事件处理'} + if (LListen <> nil) then + begin + if (LEvent.Filter = EVFILT_READ) then + _HandleAccept(LListen); + end else + if (LConnection <> nil) then + begin + LConnection._Release; + + // kqueue的读写事件同一时间只可能触发一个 + if (LEvent.Filter = EVFILT_READ) then + _HandleRead(LConnection) + else if (LEvent.Filter = EVFILT_WRITE) then + begin + if (LConnection.ConnectStatus = csConnecting) then + _HandleConnect(LConnection) + else + _HandleWrite(LConnection); + end; + end; + {$endregion} + end; + + Result := True; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSocket.pas b/ThirdParty/DCS/Net/Net.CrossSocket.pas new file mode 100644 index 00000000..5f55d0fe --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSocket.pas @@ -0,0 +1,55 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSocket; + +interface + +uses + Net.CrossSocket.Base, + {$IFDEF MSWINDOWS} + Net.CrossSocket.Iocp + {$ELSEIF defined(MACOS) or defined(IOS)} + Net.CrossSocket.Kqueue + {$ELSEIF defined(LINUX) or defined(ANDROID)} + Net.CrossSocket.Epoll + {$ENDIF}; + +type + TCrossListen = + {$IFDEF MSWINDOWS} + TIocpListen + {$ELSEIF defined(MACOS) or defined(IOS)} + TKqueueListen + {$ELSEIF defined(LINUX) or defined(ANDROID)} + TEpollListen + {$ENDIF}; + + TCrossConnection = + {$IFDEF MSWINDOWS} + TIocpConnection + {$ELSEIF defined(MACOS) or defined(IOS)} + TKqueueConnection + {$ELSEIF defined(LINUX) or defined(ANDROID)} + TEpollConnection + {$ENDIF}; + + TCrossSocket = + {$IFDEF MSWINDOWS} + TIocpCrossSocket + {$ELSEIF defined(MACOS) or defined(IOS)} + TKqueueCrossSocket + {$ELSEIF defined(LINUX) or defined(ANDROID)} + TEpollCrossSocket + {$ENDIF}; + +implementation + +end. + diff --git a/ThirdParty/DCS/Net/Net.CrossSslDemoCert.pas b/ThirdParty/DCS/Net/Net.CrossSslDemoCert.pas new file mode 100644 index 00000000..1abba68a --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSslDemoCert.pas @@ -0,0 +1,69 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSslDemoCert; + +interface + +const + SSL_SERVER_CERT: string = + '-----BEGIN CERTIFICATE-----' + sLineBreak + + 'MIIDRDCCAiwCAf8wDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCVVMxCzAJBgNV' + sLineBreak + + 'BAgMAkNBMQswCQYDVQQHDAJMQTEVMBMGA1UECgwMVGVzdCBSb290IENBMQswCQYD' + sLineBreak + + 'VQQLDAJJVDEbMBkGA1UEAwwSd3d3LnRlc3Ryb290Y2EuY29tMB4XDTEzMDIyNzE4' + sLineBreak + + 'MjE1NloXDTIzMDIyNTE4MjE1NlowaDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNB' + sLineBreak + + 'MQswCQYDVQQHDAJMQTEVMBMGA1UECgwMVGVzdCBDb21wYW55MQswCQYDVQQLDAJJ' + sLineBreak + + 'VDEbMBkGA1UEAwwSd3d3LnRlc3RzZXJ2ZXIuY29tMIIBIjANBgkqhkiG9w0BAQEF' + sLineBreak + + 'AAOCAQ8AMIIBCgKCAQEAzkHv+S30g5Dc+F1RJ1PUq9Hbh1YkEUJdYEj7ti+UfONV' + sLineBreak + + 'NOT24hXzg8zaNSVO2Bhm+l8vzOVYMnjK9xcGSq5R5I633+lEeFdxURfsSJv9Vymq' + sLineBreak + + 'tHUj5eNkmjzWBVrf4HvnZTJtRJljs941zYUgyJT9tkQXaerGFKJ6sfdXYfhGrkuK' + sLineBreak + + 'gA1e71TwpRFYcfyYbQ3htENTh2CFBv7l5gjrITcmEJwpcU3U4nx4ZTr0IPLmV2kr' + sLineBreak + + 'K8IJysY4dqgRcmduEI5ZgbYGkdG8L7QjggFXA6QNDPu8DfmXeeqS0gIffEm22bk7' + sLineBreak + + 'b2fMnPfnFsJLsDdyhgrdYktnWhtZNij0y80tV4YCTwIDAQABMA0GCSqGSIb3DQEB' + sLineBreak + + 'BQUAA4IBAQDMLn9VnUQt6BWx73J1lExYO/LWulMOnMR/WSVFy9dSwry+E807ekMY' + sLineBreak + + 'WC8b3gpgDIqfkZjmttE9VtAdss2Baten+oBW+K13339sxHvcn30OxOs/Bln0yvaZ' + sLineBreak + + 'Be+Zir7iE450b1IdYI98PMTSKgrK2e3vx/uUOCgG2yvs6/1v5rz5er/M1SQNzdMS' + sLineBreak + + 'blelHWRQ1/ExwoUWBfIBkx/A4lTPmLgoC9fnXSiLhHKbZdfCJD8KLzEV0Se+ocn/' + sLineBreak + + 'vl+6tlcUznap0TsRQpC67T/NGUimxdAhb6G1/U6z9bq0QQIuDxpOIpvwIgLvfRFx' + sLineBreak + + 'qZQxmxOcK28fejHngmek7ZJNYKQbNewP' + sLineBreak + + '-----END CERTIFICATE-----' + sLineBreak; + + SSL_SERVER_PKEY: string = + '-----BEGIN PRIVATE KEY-----' + sLineBreak + + 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOQe/5LfSDkNz4' + sLineBreak + + 'XVEnU9Sr0duHViQRQl1gSPu2L5R841U05PbiFfODzNo1JU7YGGb6Xy/M5VgyeMr3' + sLineBreak + + 'FwZKrlHkjrff6UR4V3FRF+xIm/1XKaq0dSPl42SaPNYFWt/ge+dlMm1EmWOz3jXN' + sLineBreak + + 'hSDIlP22RBdp6sYUonqx91dh+EauS4qADV7vVPClEVhx/JhtDeG0Q1OHYIUG/uXm' + sLineBreak + + 'COshNyYQnClxTdTifHhlOvQg8uZXaSsrwgnKxjh2qBFyZ24QjlmBtgaR0bwvtCOC' + sLineBreak + + 'AVcDpA0M+7wN+Zd56pLSAh98SbbZuTtvZ8yc9+cWwkuwN3KGCt1iS2daG1k2KPTL' + sLineBreak + + 'zS1XhgJPAgMBAAECggEAIT83s27Y7yw2skI4hqJYsamOPW6BOdb8vjyFdoSM5uSu' + sLineBreak + + 'I2yU7zSioCgxNEfjQaoNT2ZwihKd+OTHsrSfawJWaQUoVot/YfaWaX/1sm6Sk64/' + sLineBreak + + 'uf733mKdIM+VoB9Z3xGZ5xIN0vT2wVOcUJiZBDwf+XVYYNZbP5BBPtaj20LuAcIZ' + sLineBreak + + 'OmW9uigdXQkQ1dylUkRPitjJ92bbysrTr621JTBSmvKnF7ctcF/Ql6VfS5RcqzYI' + sLineBreak + + '6U1vozoFkjmUnExlYZHC6qKCFG73Z+IcC7ojdMpzMp4/EqiveV/9EVdFlLRB1YAa' + sLineBreak + + 'tND93xU9mo7L26XQzy79Xf2dWRUgUvaJ/7EvLA1RoQKBgQD2ZhJ9ogqfQ0ahq0D6' + sLineBreak + + '5neZo6bPbckEKshv1GKR5ixnYpPp1kCIxM8oIzb9fOvTX4MOMeRzPJyrJNwhVgfY' + sLineBreak + + 'otWLrvkNviGHXN0frmkdj/Y/WSWh7clzzwXmGbB/8NPG4yzREvQ8vhKBkAmZln6K' + sLineBreak + + 'ICl8J5NxOxF6GgYJ793GcsfZVQKBgQDWS3DYMVQ3eRgFajkQ/8+Gacgdu+8/SyM1' + sLineBreak + + 'WptHOlPvKfqg3nZYPlAjMnVmk0Q7l/d2EtFBPP07/Jz0IvC/pMz0S8XfW/NigcRn' + sLineBreak + + '0R5Nci3BXbmQEjxNGt0m0sX4C4/Bx8ei8pugipX96OemT/bWP05RskL6tWsofGsb' + sLineBreak + + '8zgIQcldEwKBgCyx90iyzBp3qahJ2E+q3qcP+IJH9965pAIlFHxCtGtMhmg0ZSBq' + sLineBreak + + 'EunE+YSh1GVTPgKlKjt9Ey44UXX6lRHG99WOt762bn6Pac0FZivmoVR8Z0coSxKm' + sLineBreak + + 'yvsiTdHnbYL2UnraZVNfZxv5dMRXeDy1+NB8nVI81L7BWbcTu7bzuyzBAoGAY0j4' + sLineBreak + + 's3HHbxwvwPKCFhovcDs6eGxGYLDTUzjzkIC5uqlccYQgmKnmPyh1tFyu1F2ITbBS' + sLineBreak + + 'O0OioFRd887sdB5KxzUELIRRs2YkNWVyALfR8zEVdGa+gYrcw8wL5OyWYlXJbPmy' + sLineBreak + + 'mSMcc1OhYDDUUFdsVfWdisLbLxrWFVEOuOSiAvkCgYEA2viHsxoFxOrhnZQOhaLT' + sLineBreak + + 'RPrgaSojv9pooHQ6fJwplewt91tb1OchDIeZk9Sl1hqPAXB0167of43GDOw2vfnq' + sLineBreak + + 'Ust7RtiyJhQhSkz0qp4aH4P9l+dZJIWnpgjcyWkcz893br9gEuVnQgh13V/lcxOn' + sLineBreak + + 'JtpaCFuHNTU3PcFiuQW+cN0=' + sLineBreak + + '-----END PRIVATE KEY-----' + sLineBreak; + +implementation + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSslServer.pas b/ThirdParty/DCS/Net/Net.CrossSslServer.pas new file mode 100644 index 00000000..fb4e0bf1 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSslServer.pas @@ -0,0 +1,142 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSslServer; + +interface + +uses + System.SysUtils, + Net.SocketAPI, + Net.CrossSocket.Base, + Net.CrossSslSocket.Base, + Net.CrossSslSocket; + +type + ICrossSslServer = interface(ICrossSslSocket) + ['{DAEB2898-1EC4-4BCF-9BEB-078B582173AB}'] + function GetAddr: string; + function GetPort: Word; + function GetActive: Boolean; + + procedure SetAddr(const Value: string); + procedure SetPort(const Value: Word); + procedure SetActive(const Value: Boolean); + + procedure Start(const ACallback: TProc = nil); + procedure Stop; + + property Addr: string read GetAddr write SetAddr; + property Port: Word read GetPort write SetPort; + + property Active: Boolean read GetActive write SetActive; + end; + + TCrossSslServer = class(TCrossSslSocket, ICrossSslServer) + private + FPort: Word; + FAddr: string; + FStarted: Integer; + + function GetAddr: string; + function GetPort: Word; + function GetActive: Boolean; + + procedure SetAddr(const Value: string); + procedure SetPort(const Value: Word); + procedure SetActive(const Value: Boolean); + public + constructor Create(AIoThreads: Integer); override; + + procedure Start(const ACallback: TProc = nil); + procedure Stop; + + property Addr: string read GetAddr write SetAddr; + property Port: Word read GetPort write SetPort; + property Active: Boolean read GetActive write SetActive; + end; + +implementation + +{ TCrossSslServer } + +constructor TCrossSslServer.Create(AIoThreads: Integer); +begin + inherited; +end; + +function TCrossSslServer.GetActive: Boolean; +begin + Result := (AtomicCmpExchange(FStarted, 0, 0) = 1); +end; + +function TCrossSslServer.GetAddr: string; +begin + Result := FAddr; +end; + +function TCrossSslServer.GetPort: Word; +begin + Result := FPort; +end; + +procedure TCrossSslServer.SetActive(const Value: Boolean); +begin + if Value then + Start + else + Stop; +end; + +procedure TCrossSslServer.SetAddr(const Value: string); +begin + FAddr := Value; +end; + +procedure TCrossSslServer.SetPort(const Value: Word); +begin + FPort := Value; +end; + +procedure TCrossSslServer.Start(const ACallback: TProc); +begin + if (AtomicExchange(FStarted, 1) = 1) then + begin + if Assigned(ACallback) then + ACallback(False); + + Exit; + end; + + StartLoop; + + Listen(FAddr, FPort, + procedure(AListen: ICrossListen; ASuccess: Boolean) + begin + if not ASuccess then + AtomicExchange(FStarted, 0); + + // Ǽ˿ + // ڼɹ֮ʵʵĶ˿ȡ + if (FPort = 0) then + FPort := AListen.LocalPort; + + if Assigned(ACallback) then + ACallback(ASuccess); + end); +end; + +procedure TCrossSslServer.Stop; +begin + CloseAll; + StopLoop; + AtomicExchange(FStarted, 0); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSslSocket.Base.pas b/ThirdParty/DCS/Net/Net.CrossSslSocket.Base.pas new file mode 100644 index 00000000..fdb67c65 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSslSocket.Base.pas @@ -0,0 +1,85 @@ +unit Net.CrossSslSocket.Base; + +interface + +uses + Net.CrossSocket.Base; + +type + /// + /// SSL Socket + /// + /// + /// ȷʹò: + /// + /// + /// SetCertificateificate SetCertificateificateFile + /// + /// + /// SetPrivateKey SetPrivateKeyFile, ͻ˲Ҫһ + /// + /// + /// Connect / Listen + /// + /// + /// + ICrossSslSocket = interface(ICrossSocket) + ['{A4765486-A0F1-4EFD-BC39-FA16AED21A6A}'] + /// + /// ڴ֤ + /// + /// + /// ֤黺 + /// + /// + /// ֤黺С + /// + procedure SetCertificate(ACertBuf: Pointer; ACertBufSize: Integer); overload; + + /// + /// ַ֤ + /// + /// + /// ַ֤ + /// + procedure SetCertificate(const ACertStr: string); overload; + + /// + /// ļ֤ + /// + /// + /// ֤ļ + /// + procedure SetCertificateFile(const ACertFile: string); + + /// + /// ڴ˽Կ + /// + /// + /// ˽Կ + /// + /// + /// ˽ԿС + /// + procedure SetPrivateKey(APKeyBuf: Pointer; APKeyBufSize: Integer); overload; + + /// + /// ַ˽Կ + /// + /// + /// ˽Կַ + /// + procedure SetPrivateKey(const APKeyStr: string); overload; + + /// + /// ļ˽Կ + /// + /// + /// ˽Կļ + /// + procedure SetPrivateKeyFile(const APKeyFile: string); + end; + +implementation + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSslSocket.MbedTls.pas b/ThirdParty/DCS/Net/Net.CrossSslSocket.MbedTls.pas new file mode 100644 index 00000000..1e785061 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSslSocket.MbedTls.pas @@ -0,0 +1,540 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSslSocket.MbedTls; + +{ + SSL通讯基本流程: + 1. 当连接建立时进行 SSL 握手, 收到数据时也要检查握手状态 + 2. 发送数据: 用 SSL_write 写入原数据, BIO_read 读取加密后的数据进行发送 + 3. 接收数据: 用 BIO_write 写入收到的数据, 用 SSL_read 读取解密后的数据 + + 传输层安全协议: + https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E5%8D%94%E8%AD%B0 +} + +interface + +uses + System.SysUtils, + System.Classes, + System.IOUtils, + Net.CrossSocket.Base, + Net.CrossSocket, + Net.CrossSslSocket.Base, + Net.MbedTls, + Net.MbedBIO; + +const + DEFAULT_CIPHERSUITES_SERVER: array [0..12] of Integer = ( + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + + MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA, + + 0); + + DEFAULT_CIPHERSUITES_CLIENT: array [0..18] of Integer = ( + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + + MBEDTLS_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + + MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA, + + MBEDTLS_TLS_RSA_WITH_3DES_EDE_CBC_SHA, + + 0); + +type + EMbedTls = class(Exception) + private + FCode: Integer; + public + constructor Create(const ACode: Integer; const AMessage: string); reintroduce; overload; + constructor Create(const ACode: Integer; const AFmt: string; const AArgs: array of const); reintroduce; overload; + + property Code: Integer read FCode; + end; + + TCrossMbedTlsConnection = class(TCrossConnection) + private + FSsl: TMbedtls_SSL_Context; + FSslBIO, FAppBIO: PBIO; + + procedure _Lock; inline; + procedure _Unlock; inline; + + function _SslHandshake: Boolean; + procedure _SendBIOPendingData(const ACallback: TProc = nil); + protected + procedure DirectSend(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc = nil); override; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); override; + destructor Destroy; override; + end; + + /// + /// 若要继承该类, 请重载 LogicXXX, 而不是 TriggerXXX + /// + TCrossMbedTlsSocket = class(TCrossSocket, ICrossSslSocket) + private const + SSL_BUF_SIZE = 32768; + private class threadvar + FSslInBuf: array [0..SSL_BUF_SIZE-1] of Byte; + private + FSrvConf, FCliConf: TMbedtls_SSL_Config; + FEntropy: TMbedtls_Entropy_Context; + FCtrDrbg: TMbedtls_CTR_DRBG_Context ; + FCert: TMbedtls_X509_CRT; + FPKey: TMbedtls_PK_Context; + FCache: TMbedtls_SSL_Cache_Context; + + procedure _InitSslConf; + procedure _FreeSslConf; + + function _MbedCert(const ACertBytes: TBytes): TBytes; inline; + procedure _UpdateCert; + protected + procedure TriggerConnected(AConnection: ICrossConnection); override; + procedure TriggerReceived(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); override; + + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + public + constructor Create(AIoThreads: Integer); override; + destructor Destroy; override; + + procedure SetCertificate(ACertBuf: Pointer; ACertBufSize: Integer); overload; + procedure SetCertificate(const ACertStr: string); overload; + procedure SetCertificateFile(const ACertFile: string); + + procedure SetPrivateKey(APKeyBuf: Pointer; APKeyBufSize: Integer); overload; + procedure SetPrivateKey(const APKeyStr: string); overload; + procedure SetPrivateKeyFile(const APKeyFile: string); + end; + +implementation + +{ EMbedTls } + +constructor EMbedTls.Create(const ACode: Integer; const AMessage: string); +var + LMessage: string; +begin + FCode := ACode; + + if (AMessage <> '') then + LMessage := AMessage + MbedErrToStr(ACode) + else + LMessage := MbedErrToStr(ACode); + + inherited Create(LMessage); +end; + +constructor EMbedTls.Create(const ACode: Integer; const AFmt: string; + const AArgs: array of const); +begin + Create(ACode, Format(AFmt, AArgs)); +end; + +function MbedCheck(const ACode: Integer; const AErrMsg: string = ''): Integer; +begin + Result := ACode; + + if (ACode >= 0) then Exit; + + case ACode of + MBEDTLS_ERR_SSL_WANT_READ, MBEDTLS_ERR_SSL_WANT_WRITE:; + else + raise EMbedTls.Create(ACode, AErrMsg); + end; +end; + +{ TCrossMbedTlsConnection } + +constructor TCrossMbedTlsConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +begin + inherited; + + mbedtls_ssl_init(@FSsl); + + if (ConnectType = ctAccept) then + MbedCheck(mbedtls_ssl_setup(@Fssl, @TCrossMbedTlsSocket(Owner).FSrvConf), 'mbedtls_ssl_setup Accept:') + else + MbedCheck(mbedtls_ssl_setup(@Fssl, @TCrossMbedTlsSocket(Owner).FCliConf), 'mbedtls_ssl_setup Connect:'); + + FSslBIO := SSL_BIO_new(BIO_BIO); + FAppBIO := SSL_BIO_new(BIO_BIO); + BIO_make_bio_pair(FSslBIO, FAppBIO); + + mbedtls_ssl_set_bio(@FSsl, FSslBIO, BIO_net_send, BIO_net_recv, nil); +end; + +destructor TCrossMbedTlsConnection.Destroy; +begin + mbedtls_ssl_free(@FSsl); + BIO_free_all(FSslBIO); + BIO_free_all(FAppBIO); + + inherited; +end; + +procedure TCrossMbedTlsConnection._Lock; +begin + // mbedtls 的多线程支持比 openssl 完善 + // 调用 mbedtls_threading_set_alt 设置了相应的线程同步函数之后不用再自己 + // _Lock _Unlock 了 +// System.TMonitor.Enter(Self); +end; + +procedure TCrossMbedTlsConnection._SendBIOPendingData( + const ACallback: TProc); +var + LConnection: ICrossConnection; + LRetCode: Integer; + LBuffer: TBytesStream; + + procedure _Success; + begin + if (LBuffer <> nil) then + FreeAndNil(LBuffer); + if Assigned(ACallback) then + ACallback(LConnection, True); + end; + +begin + LConnection := Self; + LBuffer := nil; + + {$region '将BIO中已加密的数据全部读到缓存中'} + // 检查 BIO 中是否有数据 + LRetCode := BIO_ctrl_pending(FAppBIO); + if (LRetCode <= 0) then + begin + _Success; + Exit; + end; + + LBuffer := TBytesStream.Create(nil); + while (LRetCode > 0) do + begin + LBuffer.Size := LBuffer.Size + LRetCode; + + // 读取加密后的数据 + LRetCode := BIO_read(FAppBIO, PByte(LBuffer.Memory) + LBuffer.Position, LRetCode); + if (LRetCode <= 0) then Break; + + LBuffer.Position := LBuffer.Position + LRetCode; + + // 检查 BIO 中是否还有数据 + LRetCode := BIO_ctrl_pending(FAppBIO); + end; + + if (LBuffer.Memory = nil) or (LBuffer.Size <= 0) then + begin + _Success; + Exit; + end; + {$endregion} + + {$region '发送缓存中已加密的数据'} + inherited DirectSend(LBuffer.Memory, LBuffer.Size, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + FreeAndNil(LBuffer); + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); + {$endregion} +end; + +procedure TCrossMbedTlsConnection.DirectSend(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc); +var + LRetCode: Integer; +begin + LRetCode := mbedtls_ssl_write(@FSsl, ABuffer, ACount); + if (LRetCode <> ACount) then + begin + _Log('mbedtls_ssl_write, %d / %d', [LRetCode, ACount]); + end; + + // 将待发送数据加密后发送 + if (MbedCheck(LRetCode, 'mbedtls_ssl_write DirectSend:') > 0) then + _SendBIOPendingData(ACallback); +end; + +function TCrossMbedTlsConnection._SslHandshake: Boolean; +begin + // 开始握手 + Result := (MbedCheck(mbedtls_ssl_handshake(@FSsl), 'mbedtls_ssl_handshake _SslHandshake:') = 0); + _SendBIOPendingData; +end; + +procedure TCrossMbedTlsConnection._Unlock; +begin +// System.TMonitor.Exit(Self); +end; + +{ TCrossMbedTlsSocket } + +constructor TCrossMbedTlsSocket.Create(AIoThreads: Integer); +begin + inherited; + + _InitSslConf; +end; + +destructor TCrossMbedTlsSocket.Destroy; +begin + inherited; + + _FreeSslConf; +end; + +function TCrossMbedTlsSocket.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TCrossMbedTlsConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +procedure TCrossMbedTlsSocket.SetCertificate(ACertBuf: Pointer; ACertBufSize: Integer); +begin + MbedCheck(mbedtls_x509_crt_parse(@FCert, ACertBuf, ACertBufSize), 'mbedtls_x509_crt_parse SetCertificate:'); + + _UpdateCert; +end; + +procedure TCrossMbedTlsSocket.SetCertificate(const ACertStr: string); +var + LCertBytes: TBytes; +begin + LCertBytes := TEncoding.ANSI.GetBytes(ACertStr); + LCertBytes := _MbedCert(LCertBytes); + + SetCertificate(Pointer(LCertBytes), Length(LCertBytes)); +end; + +procedure TCrossMbedTlsSocket.SetCertificateFile(const ACertFile: string); +var + LCertBytes: TBytes; +begin + LCertBytes := TFile.ReadAllBytes(ACertFile); + LCertBytes := _MbedCert(LCertBytes); + + SetCertificate(Pointer(LCertBytes), Length(LCertBytes)); +end; + +procedure TCrossMbedTlsSocket.SetPrivateKey(APKeyBuf: Pointer; APKeyBufSize: Integer); +begin + MbedCheck(mbedtls_pk_parse_key(@FPKey, APKeyBuf, APKeyBufSize, nil, 0), 'mbedtls_pk_parse_key SetPrivateKey:'); + + _UpdateCert; +end; + +procedure TCrossMbedTlsSocket.SetPrivateKey(const APKeyStr: string); +var + LPKeyBytes: TBytes; +begin + LPKeyBytes := TEncoding.ANSI.GetBytes(APKeyStr); + LPKeyBytes := _MbedCert(LPKeyBytes); + + SetPrivateKey(Pointer(LPKeyBytes), Length(LPKeyBytes)); +end; + +procedure TCrossMbedTlsSocket.SetPrivateKeyFile(const APKeyFile: string); +var + LPKeyBytes: TBytes; +begin + LPKeyBytes := TFile.ReadAllBytes(APKeyFile); + LPKeyBytes := _MbedCert(LPKeyBytes); + + SetPrivateKey(Pointer(LPKeyBytes), Length(LPKeyBytes)); +end; + +procedure TCrossMbedTlsSocket.TriggerConnected(AConnection: ICrossConnection); +var + LConnection: TCrossMbedTlsConnection; +begin + LConnection := AConnection as TCrossMbedTlsConnection; + + // 网络连接已建立, 等待握手 + LConnection.ConnectStatus := csHandshaking; + + if LConnection._SslHandshake then + begin + LConnection.ConnectStatus := csConnected; + inherited TriggerConnected(AConnection); + end; +end; + +procedure TCrossMbedTlsSocket.TriggerReceived(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +var + LConnection: TCrossMbedTlsConnection; + LRetCode: Integer; +begin + LConnection := AConnection as TCrossMbedTlsConnection; + + LConnection._Lock; + try + // 将收到的加密数据写入 BIO + LRetCode := BIO_write(LConnection.FAppBIO, ABuf, ALen); + if (LRetCode <= 0) then + begin + _Log('BIO_write, error: %d', [LRetCode]); + LConnection.Close; + Exit; + end; + + if (LRetCode <> ALen) then + begin + _Log('BIO_write, %d / %d', [LRetCode, ALen]); + end; + + // 握手 + if (LConnection.ConnectStatus = csHandshaking) then + begin + // 已完成握手才视为连接真正建立 + if LConnection._SslHandshake then + begin + LConnection.ConnectStatus := csConnected; + inherited TriggerConnected(AConnection); + end else + Exit; + end; + + while True do + begin + // 读取解密后的数据 + LRetCode := mbedtls_ssl_read(@LConnection.FSsl, @FSslInBuf, SSL_BUF_SIZE); + + if (LRetCode > 0) then + begin + inherited TriggerReceived(AConnection, @FSslInBuf, LRetCode); + end else + begin + case LRetCode of + MBEDTLS_ERR_SSL_WANT_READ, MBEDTLS_ERR_SSL_WANT_WRITE:; + else + _Log('mbedtls_ssl_read, error: %d', [LRetCode]); + LConnection.Close; + end; + Break; + end; + end; + finally + LConnection._Unlock; + end; +end; + +procedure TCrossMbedTlsSocket._FreeSslConf; +begin + mbedtls_ssl_config_free(@FSrvConf); + mbedtls_ssl_config_free(@FCliConf); + + mbedtls_x509_crt_free(@FCert); + mbedtls_pk_free(@FPKey); + + mbedtls_ctr_drbg_free(@FCtrDrbg); + mbedtls_entropy_free(@FEntropy); + mbedtls_ssl_cache_free(@FCache); +end; + +procedure TCrossMbedTlsSocket._InitSslConf; +begin + mbedtls_x509_crt_init(@FCert); + mbedtls_pk_init(@FPKey); + mbedtls_ctr_drbg_init(@FCtrDrbg); + mbedtls_entropy_init(@FEntropy); + mbedtls_ssl_cache_init(@FCache); + + MbedCheck(mbedtls_ctr_drbg_seed(@FCtrDrbg, mbedtls_entropy_func, @FEntropy, nil, 0), 'mbedtls_ctr_drbg_seed:'); + + {$region '服务端SSL配置'} + mbedtls_ssl_config_init(@FSrvConf); + mbedtls_ssl_conf_rng(@FSrvConf, mbedtls_ctr_drbg_random, @FCtrDrbg); + mbedtls_ssl_conf_authmode(@FSrvConf, MBEDTLS_SSL_VERIFY_OPTIONAL); + mbedtls_ssl_conf_session_cache(@FSrvConf, @FCache, mbedtls_ssl_cache_get, mbedtls_ssl_cache_set); // 仅服务端有效 + mbedtls_ssl_conf_ciphersuites(@FSrvConf, PInteger(@DEFAULT_CIPHERSUITES_SERVER)); + mbedtls_ssl_conf_min_version(@FSrvConf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); // TLS v1.2 + MbedCheck(mbedtls_ssl_config_defaults(@FSrvConf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT), 'mbedtls_ssl_config_defaults FSrvConf:'); + {$endregion} + + {$region '客户端SSL配置'} + mbedtls_ssl_config_init(@FCliConf); + mbedtls_ssl_conf_rng(@FCliConf, mbedtls_ctr_drbg_random, @FCtrDrbg); + mbedtls_ssl_conf_authmode(@FCliConf, MBEDTLS_SSL_VERIFY_OPTIONAL); + MbedCheck(mbedtls_ssl_config_defaults(@FCliConf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT), 'mbedtls_ssl_config_defaults FCliConf:'); + mbedtls_ssl_conf_ciphersuites(@FCliConf, PInteger(@DEFAULT_CIPHERSUITES_CLIENT)); + {$endregion} +end; + +function TCrossMbedTlsSocket._MbedCert(const ACertBytes: TBytes): TBytes; +begin + // PEM格式的证书需要以#0结尾 + if (ACertBytes = nil) + or (ACertBytes[High(ACertBytes)] = 0) then + Result := ACertBytes + else + Result := ACertBytes + [0]; +end; + +procedure TCrossMbedTlsSocket._UpdateCert; +begin + // 尚未加载证书 + if (FCert.version = 0) then Exit; + + mbedtls_ssl_conf_ca_chain(@FCliConf, @FCert, nil); + + // 尚未加载私钥 + if (FPKey.pk_info = nil) then Exit; + + if (FCert.next <> nil) then + mbedtls_ssl_conf_ca_chain(@FSrvConf, FCert.next, nil); + + MbedCheck(mbedtls_ssl_conf_own_cert(@FSrvConf, @FCert, @FPKey), 'mbedtls_ssl_conf_own_cert:'); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSslSocket.OpenSSL.pas b/ThirdParty/DCS/Net/Net.CrossSslSocket.OpenSSL.pas new file mode 100644 index 00000000..a54007f1 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSslSocket.OpenSSL.pas @@ -0,0 +1,507 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossSslSocket.OpenSSL; + +{ + SSL通讯基本流程: + 1. 当连接建立时进行 SSL 握手, 收到数据时也要检查握手状态 + 2. 发送数据: 用 SSL_write 写入原数据, BIO_read 读取加密后的数据进行发送 + 3. 接收数据: 用 BIO_write 写入收到的数据, 用 SSL_read 读取解密后的数据 + + OpenSSL 的 SSL 对象不是线程安全的!!!!!!!!!!!!!!!!! + 即便初始化 OpenSSL 时设置了那几个线程相关的回调也一样, + 一定要保证同一时间不能有两个或以上的线程访问到同一个 SSL 对象 + 或者与其绑定的 BIO 对象 + + 由于收发数据需要解密及加密, 还需要用临界区保护 SSL 对象, + 所以效率比不使用 SSL 时会下降很多, 这是无法避免的 + + 传输层安全协议: + https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E5%8D%94%E8%AD%B0 +} + +interface + +uses + System.SysUtils, + System.Classes, + Net.CrossSocket.Base, + Net.CrossSocket, + Net.CrossSslSocket.Base, + Net.OpenSSL; + +type + TCrossOpenSslConnection = class(TCrossConnection) + private + FSsl: PSSL; + FBIOIn, FBIOOut: PBIO; + FSslLock: TObject; + + procedure _SslLock; inline; + procedure _SslUnlock; inline; + + function _SslHandshake: Boolean; + procedure _WriteBioToSocket(const ACallback: TProc = nil); + protected + procedure DirectSend(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc = nil); override; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); override; + destructor Destroy; override; + end; + + /// + /// 若要继承该类, 请重载 LogicXXX, 而不是 TriggerXXX + /// + TCrossOpenSslSocket = class(TCrossSocket, ICrossSslSocket) + private const + SSL_BUF_SIZE = 32768; + private class threadvar + FSslInBuf: array [0..SSL_BUF_SIZE-1] of Byte; + private + FSslCtx: PSSL_CTX; + + procedure _InitSslCtx; + procedure _FreeSslCtx; + protected + procedure TriggerConnected(AConnection: ICrossConnection); override; + procedure TriggerReceived(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); override; + + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + public + constructor Create(AIoThreads: Integer); override; + destructor Destroy; override; + + procedure SetCertificate(ACertBuf: Pointer; ACertBufSize: Integer); overload; + procedure SetCertificate(const ACertStr: string); overload; + procedure SetCertificateFile(const ACertFile: string); + + procedure SetPrivateKey(APKeyBuf: Pointer; APKeyBufSize: Integer); overload; + procedure SetPrivateKey(const APKeyStr: string); overload; + procedure SetPrivateKeyFile(const APKeyFile: string); + end; + +implementation + +{ TCrossOpenSslConnection } + +constructor TCrossOpenSslConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +begin + inherited; + + FSslLock := TObject.Create; + + FSsl := SSL_new(TCrossOpenSslSocket(Owner).FSslCtx); + FBIOIn := BIO_new(BIO_s_mem()); + FBIOOut := BIO_new(BIO_s_mem()); + SSL_set_bio(FSsl, FBIOIn, FBIOOut); + + if (ConnectType = ctAccept) then + SSL_set_accept_state(FSsl) // 服务端连接 + else + SSL_set_connect_state(FSsl); // 客户端连接 +end; + +destructor TCrossOpenSslConnection.Destroy; +begin + _SslLock; + try + if (SSL_shutdown(FSsl) = 0) then + SSL_shutdown(FSsl); + SSL_free(FSsl); + finally + _SslUnlock; + end; + FreeAndNil(FSslLock); + + inherited; +end; + +procedure TCrossOpenSslConnection._WriteBioToSocket( + const ACallback: TProc); +var + LConnection: ICrossConnection; + ret, error: Integer; + LBuffer: TBytesStream; + + procedure _Success; + begin + if (LBuffer <> nil) then + FreeAndNil(LBuffer); + if Assigned(ACallback) then + ACallback(LConnection, True); + end; + + procedure _Failed; + begin + if (LBuffer <> nil) then + FreeAndNil(LBuffer); + LConnection.Close; + if Assigned(ACallback) then + ACallback(LConnection, False); + end; + +begin + LConnection := Self; + LBuffer := nil; + + {$region '将BIO中已加密的数据全部读到缓存中'} + // 从BIO中读取数据这一段必须全读出来再发送 + // 因为SSL对象本身并不是线程安全的, 如果读取数据的同时, 另一个线程尝试操作SSL对象 + // 就会引起异常, 所以这里将读取数据和发送数据分成两部分, 将数据全读出来之后 + // 再调用异步发送, 方便在外层包裹加锁 + ret := BIO_pending(FBIOOut); + if (ret <= 0) then + begin + _Success; + Exit; + end; + + LBuffer := TBytesStream.Create(nil); + while (ret > 0) do + begin + LBuffer.Size := LBuffer.Size + ret; + + // 读取加密后的数据 + ret := BIO_read(FBIOOut, PByte(LBuffer.Memory) + LBuffer.Position, ret); + error := SSL_get_error(FSsl, ret); + if ssl_is_fatal_error(error) then + begin + _Failed; + Exit; + end; + + if (ret <= 0) then Break; + + LBuffer.Position := LBuffer.Position + ret; + + // 检查 BIO 中是否有数据 + ret := BIO_pending(FBIOOut); + end; + + if (LBuffer.Memory = nil) or (LBuffer.Size <= 0) then + begin + _Success; + Exit; + end; + {$endregion} + + {$region '发送缓存中已加密的数据'} + inherited DirectSend(LBuffer.Memory, LBuffer.Size, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + FreeAndNil(LBuffer); + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); + {$endregion} +end; + +procedure TCrossOpenSslConnection.DirectSend(ABuffer: Pointer; ACount: Integer; + const ACallback: TProc); +var + LConnection: ICrossConnection; + ret, error: Integer; + + procedure _Failed; + begin + if Assigned(ACallback) then + ACallback(LConnection, False); + end; + +begin + LConnection := Self; + + // 将待发送数据加密 + // SSL_write 默认会将全部数据写入成功才返回, + // 除非调用 SSL_CTX_set_mode 设置了 SSL_MODE_ENABLE_PARTIAL_WRITE 参数 + // 才会出现部分写入成功即返回的情况。这里并没有设置该参数,所以无需做 + // 部分数据的处理,只需要一次 SSL_Write 调用即可。 + _SslLock; + try + ret := SSL_write(FSsl, ABuffer, ACount); + if (ret > 0) then + _WriteBioToSocket(ACallback) + else + begin + error := SSL_get_error(FSsl, ret); + _Log('SSL_write error %d %s', [error, ssl_error_message(error)]); + case error of + SSL_ERROR_WANT_READ:; + SSL_ERROR_WANT_WRITE: _WriteBioToSocket; + else + _Failed; + end; + end; + finally + _SslUnlock; + end; +end; + +procedure TCrossOpenSslConnection._SslLock; +begin + TMonitor.Enter(FSslLock); +end; + +procedure TCrossOpenSslConnection._SslUnlock; +begin + TMonitor.Exit(FSslLock); +end; + +function TCrossOpenSslConnection._SslHandshake: Boolean; +var + ret, error: Integer; +begin + Result := False; + + _SslLock; + try + // 开始握手 + ret := SSL_do_handshake(FSsl); + if (ret = 1) then + begin + _WriteBioToSocket; + Exit(True); + end; + + error := SSL_get_error(FSsl, ret); + if ssl_is_fatal_error(error) then + begin + {$IFDEF DEBUG} + _Log('SSL_do_handshake error %s', [ssl_error_message(error)]); + {$ENDIF} + Close; + end else + _WriteBioToSocket; + finally + _SslUnlock; + end; +end; + +{ TCrossOpenSslSocket } + +constructor TCrossOpenSslSocket.Create(AIoThreads: Integer); +begin + inherited; + + TSSLTools.LoadSSL; + _InitSslCtx; +end; + +destructor TCrossOpenSslSocket.Destroy; +begin + inherited; + + _FreeSslCtx; + TSSLTools.UnloadSSL; +end; + +function TCrossOpenSslSocket.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TCrossOpenSslConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +procedure TCrossOpenSslSocket._InitSslCtx; +var + LEcdh: PEC_KEY; +begin + if (FSslCtx <> nil) then Exit; + + FSslCtx := TSSLTools.NewCTX(SSLv23_method()); + + SSL_CTX_set_verify(FSslCtx, SSL_VERIFY_NONE, nil); + + SSL_CTX_set_mode(FSslCtx, SSL_MODE_AUTO_RETRY); + + {$region '采用新型加密套件进行加密'} + SSL_CTX_set_options(FSslCtx, + // 不使用已经不安全的 SSLv2 和 SSv3 + SSL_OP_NO_SSLv2 or SSL_OP_NO_SSLv3 or + // 启用各种漏洞解决方案(适用于 0.9.7 之前的版本) + SSL_OP_ALL or + // 总是使用 SSL_CTX_set_tmp_ecdh/SSL_set_tmp_ecdh 设置的参数创建新 KEY + SSL_OP_SINGLE_ECDH_USE or + // 根据服务器偏好选择加密套件 + SSL_OP_CIPHER_SERVER_PREFERENCE + ); + + // 设置加密套件的使用顺序 + SSL_CTX_set_cipher_list(FSslCtx, + // from nodejs(node_constants.h) + // #define DEFAULT_CIPHER_LIST_CORE + 'ECDHE-ECDSA-AES128-GCM-SHA256:' + + 'ECDHE-RSA-AES128-GCM-SHA256:' + + 'ECDHE-RSA-AES256-GCM-SHA384:' + + 'ECDHE-ECDSA-AES256-GCM-SHA384:' + + 'DHE-RSA-AES128-GCM-SHA256:' + + 'ECDHE-RSA-AES128-SHA256:' + + 'DHE-RSA-AES128-SHA256:' + + 'ECDHE-RSA-AES256-SHA384:' + + 'DHE-RSA-AES256-SHA384:' + + 'ECDHE-RSA-AES256-SHA256:' + + 'DHE-RSA-AES256-SHA256:' + + 'HIGH:' + + '!aNULL:' + + '!eNULL:' + + '!EXPORT:' + + '!DES:' + + '!RC4:' + + '!MD5:' + + '!PSK:' + + '!SRP:' + + '!CAMELLIA' + ); + + LEcdh := EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (LEcdh <> nil) then + begin + SSL_CTX_set_tmp_ecdh(FSslCtx, LEcdh); + EC_KEY_free(LEcdh); + end; + {$endregion} +end; + +procedure TCrossOpenSslSocket._FreeSslCtx; +begin + if (FSslCtx = nil) then Exit; + + TSSLTools.FreeCTX(FSslCtx); +end; + +procedure TCrossOpenSslSocket.SetCertificate(ACertBuf: Pointer; + ACertBufSize: Integer); +begin + TSSLTools.SetCertificate(FSslCtx, ACertBuf, ACertBufSize); +end; + +procedure TCrossOpenSslSocket.SetCertificate(const ACertStr: string); +begin + TSSLTools.SetCertificate(FSslCtx, ACertStr); +end; + +procedure TCrossOpenSslSocket.SetCertificateFile(const ACertFile: string); +begin + TSSLTools.SetCertificateFile(FSslCtx, ACertFile); +end; + +procedure TCrossOpenSslSocket.SetPrivateKey(APKeyBuf: Pointer; + APKeyBufSize: Integer); +begin + TSSLTools.SetPrivateKey(FSslCtx, APKeyBuf, APKeyBufSize); +end; + +procedure TCrossOpenSslSocket.SetPrivateKey(const APKeyStr: string); +begin + TSSLTools.SetPrivateKey(FSslCtx, APKeyStr); +end; + +procedure TCrossOpenSslSocket.SetPrivateKeyFile(const APKeyFile: string); +begin + TSSLTools.SetPrivateKeyFile(FSslCtx, APKeyFile); +end; + +procedure TCrossOpenSslSocket.TriggerConnected(AConnection: ICrossConnection); +var + LConnection: TCrossOpenSslConnection; +begin + LConnection := AConnection as TCrossOpenSslConnection; + + LConnection._SslLock; + try + // 网络连接已建立, 等待握手 + LConnection.ConnectStatus := csHandshaking; + + // 已完成握手才视为连接真正建立 + if LConnection._SslHandshake then + begin + LConnection.ConnectStatus := csConnected; + inherited TriggerConnected(AConnection); + end; + finally + LConnection._SslUnlock; + end; +end; + +procedure TCrossOpenSslSocket.TriggerReceived(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +var + LConnection: TCrossOpenSslConnection; + ret, error: Integer; + LBuffer: TBytesStream; +begin + LConnection := AConnection as TCrossOpenSslConnection; + LConnection._SslLock; + try + // 将收到的加密数据写入 BIO, 让 OpenSSL 对其解密 + while True do + begin + ret := BIO_write(LConnection.FBIOIn, ABuf, ALen); +// _Log('recv %d, bio_write %d', [ALen, ret]); + if (ret > 0) then Break; + + if not BIO_should_retry(LConnection.FBIOIn) then + begin + LConnection.Close; + Exit; + end; + end; + + // 未完成初始化, 继续握手 + if not SSL_is_init_finished(LConnection.FSsl) then + begin + // 已完成握手才视为连接真正建立 + if LConnection._SslHandshake + and (LConnection.ConnectStatus = csHandshaking) then + begin + LConnection.ConnectStatus := csConnected; + inherited TriggerConnected(AConnection); + end; + Exit; + end; + + LBuffer := TBytesStream.Create(nil); + try + while True do + begin + // 貌似每次读出来的数据都不会超过 16K + ret := SSL_read(LConnection.FSsl, @FSslInBuf[0], SSL_BUF_SIZE); + if (ret > 0) then + LBuffer.Write(FSslInBuf[0], ret) + else + begin + error := SSL_get_error(LConnection.FSsl, ret); +// _Log('SSL_read error %d %s', [error, ssl_error_message(error)]); + + if ssl_is_fatal_error(error) then + begin + {$IFDEF DEBUG} + _Log('SSL_read error %d %s', [error, ssl_error_message(error)]); + {$ENDIF} + LConnection.Close; + end; + Break; + end; + end; + + if (LBuffer.Size > 0) then + inherited TriggerReceived(AConnection, LBuffer.Memory, LBuffer.Size); + finally + FreeAndNil(LBuffer); + end; + finally + LConnection._SslUnlock; + end; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossSslSocket.pas b/ThirdParty/DCS/Net/Net.CrossSslSocket.pas new file mode 100644 index 00000000..de277ba5 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossSslSocket.pas @@ -0,0 +1,31 @@ +unit Net.CrossSslSocket; + +interface + +uses + Net.CrossSocket.Base, + Net.CrossSslSocket.Base + {$IFDEF __MBED_TLS__} + ,Net.CrossSslSocket.MbedTls + {$ELSE} + ,Net.CrossSslSocket.OpenSSL + {$ENDIF}; + +type + TCrossSslConnection = + {$IFDEF __MBED_TLS__} + TCrossMbedTlsConnection + {$ELSE} + TCrossOpenSslConnection + {$ENDIF}; + + TCrossSslSocket = + {$IFDEF __MBED_TLS__} + TCrossMbedTlsSocket + {$ELSE} + TCrossOpenSslSocket + {$ENDIF}; + +implementation + +end. diff --git a/ThirdParty/DCS/Net/Net.CrossWebSocketServer.pas b/ThirdParty/DCS/Net/Net.CrossWebSocketServer.pas new file mode 100644 index 00000000..71a44f8e --- /dev/null +++ b/ThirdParty/DCS/Net/Net.CrossWebSocketServer.pas @@ -0,0 +1,1067 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.CrossWebSocketServer; + +{ + The WebSocket Protocol + https://tools.ietf.org/html/rfc6455 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + opcode: + * %x0 denotes a continuation frame + * %x1 denotes a text frame + * %x2 denotes a binary frame + * %x3-7 are reserved for further non-control frames + * %x8 denotes a connection close + * %x9 denotes a ping + * %xA denotes a pong + * %xB-F are reserved for further control frames + Payload length: 7 bits, 7+16 bits, or 7+64 bits + Masking-key: 0 or 4 bytes +} + +interface + +uses + System.SysUtils, + System.Classes, + System.Hash, + System.NetEncoding, + System.Math, + System.Generics.Collections, + Net.CrossSocket.Base, + Net.CrossHttpServer; + +const + WS_OP_CONTINUATION = $00; + WS_OP_TEXT = $01; + WS_OP_BINARY = $02; + WS_OP_CLOSE = $08; + WS_OP_PING = $09; + WS_OP_PONG = $0A; + + WS_MAGIC_STR = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +type + /// + /// WebSocket连接接口 + /// + ICrossWebSocketConnection = interface(ICrossHttpConnection) + ['{15AAA55B-1671-43A1-A9CF-D3EF08D377A6}'] + /// + /// 是否WEB SOCKET + /// + function IsWebSocket: Boolean; + + /// + /// 发送关闭握手 + /// + procedure WsClose; + + /// + /// 发送无类型数据 + /// + /// + /// 无类型数据 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + procedure WsSend(const AData; ACount: NativeInt; ACallback: TProc = nil); overload; + + /// + /// 发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + procedure WsSend(const AData: TBytes; AOffset, ACount: NativeInt; ACallback: TProc = nil); overload; + + /// + /// 发送字节数据 + /// + /// + /// 字节数据 + /// + /// + /// 回调函数 + /// + procedure WsSend(const AData: TBytes; ACallback: TProc = nil); overload; + + /// + /// 发送字符串数据 + /// + /// + /// 字符串数据 + /// + /// + /// 回调函数 + /// + procedure WsSend(const AData: string; ACallback: TProc = nil); overload; + + /// + /// 发送碎片化数据 + /// + /// + /// 碎片化数据源 + /// + /// + /// 回调函数 + /// + /// + /// 回调函数说明: + /// + /// + /// 第一个参数: 待发送数据指针 + /// + /// + /// 第二个参数: 待发送数据长度 + /// + /// + /// 返回值: 数据是否有效 + /// + /// + /// 当待发送数据指针为nil或者长度小于等于0或者返回值为false时, 表示没有更多的数据需要发送了 + /// + /// + /// + procedure WsSend(const AData: TFunc; ACallback: TProc = nil); overload; + + /// + /// 发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 偏移量 + /// + /// + /// 数据大小 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure WsSend(const AData: TStream; const AOffset, ACount: Int64; ACallback: TProc = nil); overload; + + /// + /// 发送流数据 + /// + /// + /// 流数据 + /// + /// + /// 回调函数 + /// + /// + /// 必须保证发送过程中流对象的有效性, 要释放流对象可以放到回调函数中进行 + /// + procedure WsSend(const AData: TStream; ACallback: TProc = nil); overload; + end; + + TWsRequestType = (wsrtUnknown, wsrtText, wsrtBinary); + TWsOnOpen = TProc; + TWsOnMessage = reference to procedure(AConnection: ICrossWebSocketConnection; + ARequestType: TWsRequestType; const ARequestData: TBytes); + TWsOnClose = TProc; + + /// + /// 跨平台WebSocket服务器 + /// + ICrossWebSocketServer = interface(ICrossHttpServer) + ['{FF008E22-9938-4DC4-9421-083DA9EFFCDC}'] + /// + /// WebSocket连接建立时触发 + /// + /// + /// 回调函数 + /// + function OnOpen(ACallback: TWsOnOpen): ICrossWebSocketServer; + + /// + /// 收到WebSocket消息时触发 + /// + /// + /// 回调函数 + /// + function OnMessage(ACallback: TWsOnMessage): ICrossWebSocketServer; + + /// + /// WebSocket连接关闭时触发 + /// + /// + /// hui'diaohanshu + /// + function OnClose(ACallback: TWsOnClose): ICrossWebSocketServer; + end; + + TCrossWebSocketConnection = class(TCrossHttpConnection, ICrossWebSocketConnection) + private type + TWsFrameParseState = (wsHeader, wsBody, wsDone); + private + FIsWebSocket: Boolean; + FWsFrameState: TWsFrameParseState; + FWsFrameHeader, FWsRequestBody: TBytesStream; + FWsFIN: Boolean; + FWsOpCode: Byte; + FWsMask: Boolean; + FWsMaskKey: Cardinal; + FWsMaskKeyShift: Integer; + FWsPayload: Byte; + FWsHeaderSize: Byte; + FWsBodySize: UInt64; + FWsSendClose: Integer; + + procedure _WebSocketRecv(ABuf: Pointer; ALen: Integer); + procedure _ResetFrameHeader; + procedure _ResetRequest; + + procedure _AdjustOffsetCount(const ABodySize: NativeInt; var AOffset, ACount: NativeInt); overload; + procedure _AdjustOffsetCount(const ABodySize: Int64; var AOffset, ACount: Int64); overload; + + {$region '内部发送方法'} + procedure _WsSend(AOpCode: Byte; AFin: Boolean; AData: Pointer; ACount: NativeInt; + ACallback: TProc = nil); overload; + + procedure _WsSend(AOpCode: Byte; AFin: Boolean; const AData: TBytes; AOffset, ACount: NativeInt; + ACallback: TProc = nil); overload; + + procedure _WsSend(AOpCode: Byte; AFin: Boolean; const AData: TBytes; + ACallback: TProc = nil); overload; + {$endregion} + + procedure _RespondPong(const AData: TBytes); + procedure _RespondClose; + + function _OpCodeToReqType(AOpCode: Byte): TWsRequestType; + protected + procedure TriggerWsRequest(ARequestType: TWsRequestType; + const ARequestData: TBytes); virtual; + public + constructor Create(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType); override; + destructor Destroy; override; + + class function _MakeFrameHeader(AOpCode: Byte; AFin: Boolean; AMaskKey: Cardinal; ADataSize: UInt64): TBytes; static; + + function IsWebSocket: Boolean; + procedure WsClose; + + procedure WsSend(const AData; ACount: NativeInt; ACallback: TProc = nil); overload; + procedure WsSend(const AData: TBytes; AOffset, ACount: NativeInt; ACallback: TProc = nil); overload; + procedure WsSend(const AData: TBytes; ACallback: TProc = nil); overload; + procedure WsSend(const AData: string; ACallback: TProc = nil); overload; + + procedure WsSend(const AData: TFunc; ACallback: TProc = nil); overload; + procedure WsSend(const AData: TStream; const AOffset, ACount: Int64; ACallback: TProc = nil); overload; + procedure WsSend(const AData: TStream; ACallback: TProc = nil); overload; + end; + + TNetCrossWebSocketServer = class(TCrossHttpServer, ICrossWebSocketServer) + private + FOnOpenEvents: TList; + FOnMessageEvents: TList; + FOnCloseEvents: TList; + + procedure _WebSocketHandshake(AConnection: ICrossWebSocketConnection; + ACallback: TProc); + + procedure _OnOpen(AConnection: ICrossWebSocketConnection); + procedure _OnMessage(AConnection: ICrossWebSocketConnection; + ARequestType: TWsRequestType; const ARequestData: TBytes); + procedure _OnClose(AConnection: ICrossWebSocketConnection); + protected + function CreateConnection(AOwner: ICrossSocket; AClientSocket: THandle; + AConnectType: TConnectType): ICrossConnection; override; + procedure LogicReceived(AConnection: ICrossConnection; ABuf: Pointer; ALen: Integer); override; + procedure LogicDisconnected(AConnection: ICrossConnection); override; + + procedure DoOnRequest(AConnection: ICrossHttpConnection); override; + + procedure TriggerWsRequest(AConnection: ICrossWebSocketConnection; + ARequestType: TWsRequestType; const ARequestData: TBytes); virtual; + public + constructor Create(AIoThreads: Integer); override; + destructor Destroy; override; + + function OnOpen(ACallback: TWsOnOpen): ICrossWebSocketServer; + function OnMessage(ACallback: TWsOnMessage): ICrossWebSocketServer; + function OnClose(ACallback: TWsOnClose): ICrossWebSocketServer; + end; + +implementation + +uses + System.StrUtils; + +{ TCrossWebSocketConnection } + +constructor TCrossWebSocketConnection.Create(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType); +begin + inherited; + + FWsFrameHeader := TBytesStream.Create(nil); + FWsRequestBody := TBytesStream.Create(nil); + _ResetRequest; +end; + +destructor TCrossWebSocketConnection.Destroy; +begin + FreeAndNil(FWsFrameHeader); + FreeAndNil(FWsRequestBody); + inherited; +end; + +function TCrossWebSocketConnection.IsWebSocket: Boolean; +begin + Result := FIsWebSocket; +end; + +procedure TCrossWebSocketConnection.TriggerWsRequest( + ARequestType: TWsRequestType; const ARequestData: TBytes); +begin + TNetCrossWebSocketServer(Owner).TriggerWsRequest(Self, ARequestType, ARequestData); +end; + +procedure TCrossWebSocketConnection.WsSend(const AData; ACount: NativeInt; + ACallback: TProc); +begin + _WsSend(WS_OP_BINARY, True, @AData, ACount, ACallback); +end; + +procedure TCrossWebSocketConnection.WsSend(const AData: TBytes; AOffset, + ACount: NativeInt; ACallback: TProc); +begin + _WsSend(WS_OP_BINARY, True, AData, AOffset, ACount, ACallback); +end; + +procedure TCrossWebSocketConnection.WsSend(const AData: TBytes; + ACallback: TProc); +begin + WsSend(AData, 0, Length(AData), ACallback); +end; + +procedure TCrossWebSocketConnection.WsSend(const AData: string; + ACallback: TProc); +begin + _WsSend(WS_OP_TEXT, True, TEncoding.UTF8.GetBytes(AData), ACallback); +end; + +procedure TCrossWebSocketConnection.WsSend( + const AData: TFunc; + ACallback: TProc); +var + LConnection: ICrossWebSocketConnection; + LOpCode: Byte; + LSender: TProc; +begin + LConnection := Self; + LOpCode := WS_OP_BINARY; + + LSender := + procedure(AConnection: ICrossWebSocketConnection; ASuccess: Boolean) + var + LData: Pointer; + LCount: NativeInt; + begin + if not ASuccess then + begin + if Assigned(ACallback) then + ACallback(AConnection, False); + + AConnection.Close; + + LSender := nil; + + Exit; + end; + + LData := nil; + LCount := 0; + if not Assigned(AData) + or not AData(@LData, @LCount) + or (LData = nil) + or (LCount <= 0) then + begin + LSender := nil; + + // 结束帧 + // opcode 为 WS_OP_CONTINUATION + // FIN 为 1 + // 结束帧只有一个头, 因为结束帧是流无数据可读时才生成的 + TCrossWebSocketConnection(AConnection)._WsSend(LOpCode, + True, nil, 0, + procedure(AConnection: ICrossWebSocketConnection; ASuccess: Boolean) + begin + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); + + Exit; + end; + + // 第一帧及中间帧 + // 第一帧 opcode 为 WS_OP_BINARY, FIN 为 0 + // 中间帧 opcode 为 WS_OP_CONTINUATION, FIN 为 0 + TCrossWebSocketConnection(AConnection)._WsSend(LOpCode, + False, LData, LCount, LSender); + + LOpCode := WS_OP_CONTINUATION; + end; + + LSender(LConnection, True); +end; + +procedure TCrossWebSocketConnection.WsSend(const AData: TStream; const AOffset, + ACount: Int64; ACallback: TProc); +var + LOffset, LCount: Int64; + LBody: TStream; + LBuffer: TBytes; +begin + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(AData.Size, LOffset, LCount); + + if (AData is TCustomMemoryStream) then + begin + WsSend(Pointer(IntPtr(TCustomMemoryStream(AData).Memory) + LOffset)^, LCount, ACallback); + Exit; + end; + + LBody := AData; + LBody.Position := LOffset; + + SetLength(LBuffer, SND_BUF_SIZE); + + WsSend( + // BODY + function(AData: PPointer; ACount: PNativeInt): Boolean + begin + if (LCount <= 0) then Exit(False); + + AData^ := @LBuffer[0]; + ACount^ := LBody.Read(LBuffer[0], Min(LCount, SND_BUF_SIZE)); + + Result := (ACount^ > 0); + + if Result then + Dec(LCount, ACount^); + end, + // CALLBACK + procedure(AConnection: ICrossWebSocketConnection; ASuccess: Boolean) + begin + LBuffer := nil; + + if Assigned(ACallback) then + ACallback(AConnection, ASuccess); + end); +end; + +procedure TCrossWebSocketConnection.WsSend(const AData: TStream; + ACallback: TProc); +begin + WsSend(AData, 0, 0, ACallback); +end; + +procedure TCrossWebSocketConnection.WsClose; +begin + if (AtomicExchange(FWsSendClose, 1) = 1) then Exit; + + _WsSend(WS_OP_CLOSE, True, nil, 0); +end; + +procedure TCrossWebSocketConnection._ResetRequest; +begin + FWsFrameState := wsHeader; + FWsFrameHeader.Clear; + FWsRequestBody.Clear; + FWsMaskKeyShift := 0; +end; + +procedure TCrossWebSocketConnection._AdjustOffsetCount( + const ABodySize: NativeInt; var AOffset, ACount: NativeInt); +begin + {$region '修正 AOffset'} + // 偏移为正数, 从头部开始计算偏移 + if (AOffset >= 0) then + begin + AOffset := AOffset; + if (AOffset >= ABodySize) then + AOffset := ABodySize - 1; + end else + // 偏移为负数, 从尾部开始计算偏移 + begin + AOffset := ABodySize + AOffset; + if (AOffset < 0) then + AOffset := 0; + end; + {$endregion} + + {$region '修正 ACount'} + // ACount<=0表示需要处理所有数据 + if (ACount <= 0) then + ACount := ABodySize; + + if (ABodySize - AOffset < ACount) then + ACount := ABodySize - AOffset; + {$endregion} +end; + +procedure TCrossWebSocketConnection._AdjustOffsetCount(const ABodySize: Int64; + var AOffset, ACount: Int64); +begin + {$region '修正 AOffset'} + // 偏移为正数, 从头部开始计算偏移 + if (AOffset >= 0) then + begin + AOffset := AOffset; + if (AOffset >= ABodySize) then + AOffset := ABodySize - 1; + end else + // 偏移为负数, 从尾部开始计算偏移 + begin + AOffset := ABodySize + AOffset; + if (AOffset < 0) then + AOffset := 0; + end; + {$endregion} + + {$region '修正 ACount'} + // ACount<=0表示需要处理所有数据 + if (ACount <= 0) then + ACount := ABodySize; + + if (ABodySize - AOffset < ACount) then + ACount := ABodySize - AOffset; + {$endregion} +end; + +class function TCrossWebSocketConnection._MakeFrameHeader(AOpCode: Byte; + AFin: Boolean; AMaskKey: Cardinal; ADataSize: UInt64): TBytes; +var + LPayload: Byte; + LHeaderSize: Integer; +begin + LHeaderSize := 2; + if (ADataSize < 126) then + LPayload := ADataSize + else if (ADataSize <= $FFFF) then + begin + LPayload := 126; + Inc(LHeaderSize, 2); + end else + begin + LPayload := 127; + Inc(LHeaderSize, 8); + end; + if (AMaskKey <> 0) then + Inc(LHeaderSize, 4); + + SetLength(Result, LHeaderSize); + FillChar(Result[0], LHeaderSize, 0); + + if AFin then + Result[0] := Result[0] or $80; + Result[0] := Result[0] or (AOpCode and $0F); + + if (AMaskKey <> 0) then + Result[1] := Result[1] or $80; + Result[1] := Result[1] or (LPayload and $7F); + + if (LPayload = 126) then + begin + Result[2] := PByte(@ADataSize)[1]; + Result[3] := PByte(@ADataSize)[0]; + end else + if (LPayload = 127) then + begin + Result[2] := PByte(@ADataSize)[7]; + Result[3] := PByte(@ADataSize)[6]; + Result[4] := PByte(@ADataSize)[5]; + Result[5] := PByte(@ADataSize)[4]; + Result[6] := PByte(@ADataSize)[3]; + Result[7] := PByte(@ADataSize)[2]; + Result[8] := PByte(@ADataSize)[1]; + Result[9] := PByte(@ADataSize)[0]; + end; + + if (AMaskKey <> 0) then + Move(AMaskKey, Result[LHeaderSize - 4], 4); +end; + +function TCrossWebSocketConnection._OpCodeToReqType( + AOpCode: Byte): TWsRequestType; +begin + case AOpCode of + WS_OP_TEXT: Exit(wsrtText); + WS_OP_BINARY: Exit(wsrtBinary); + else + Exit(wsrtUnknown); + end; +end; + +procedure TCrossWebSocketConnection._ResetFrameHeader; +begin + FWsFrameState := wsHeader; + FWsFrameHeader.Clear; + FWsMaskKeyShift := 0; +end; + +procedure TCrossWebSocketConnection._WebSocketRecv(ABuf: Pointer; + ALen: Integer); +var + PBuf: PByte; + LByte: Byte; + LReqData: TBytes; +begin + PBuf := ABuf; + while (ALen > 0) do + begin + // 使用循环处理粘包, 比递归调用节省资源 + while (ALen > 0) and (FWsFrameState <> wsDone) do + begin + case FWsFrameState of + wsHeader: + begin + FWsFrameHeader.Write(PBuf^, 1); + Dec(ALen); + Inc(PBuf); + + if (FWsFrameHeader.Size = 2) then + begin + // 第1个字节最高位为 FIN 状态 + FWsFIN := (FWsFrameHeader.Bytes[0] and $80 <> 0); + + // 第1个字节低4位为 opcode 状态 + LByte := FWsFrameHeader.Bytes[0] and $0F; + if (LByte <> WS_OP_CONTINUATION) then + FWsOpCode := LByte; + + // 第2个字节最高位为 MASK 状态 + FWsMask := (FWsFrameHeader.Bytes[1] and $80 <> 0); + + // 第2个字节低7位为 payload len + FWsPayload := FWsFrameHeader.Bytes[1] and $7F; + + FWsHeaderSize := 2; + if (FWsPayload < 126) then + FWsBodySize := FWsPayload + else if (FWsPayload = 126) then + Inc(FWsHeaderSize, 2) + else if (FWsPayload = 127) then + Inc(FWsHeaderSize, 8); + if FWsMask then + Inc(FWsHeaderSize, 4); + end else + if (FWsFrameHeader.Size = FWsHeaderSize) then + begin + FWsFrameState := wsBody; + + // 保存 mask key + if FWsMask then + Move(PCardinal(UIntPtr(FWsFrameHeader.Memory) + FWsHeaderSize - 4)^, FWsMaskKey, 4); + + if (FWsPayload = 126) then + FWsBodySize := FWsFrameHeader.Bytes[3] + + Word(FWsFrameHeader.Bytes[2]) shl 8 + else if (FWsPayload = 127) then + FWsBodySize := FWsFrameHeader.Bytes[9] + + UInt64(FWsFrameHeader.Bytes[8]) shl 8 + + UInt64(FWsFrameHeader.Bytes[7]) shl 16 + + UInt64(FWsFrameHeader.Bytes[6]) shl 24 + + UInt64(FWsFrameHeader.Bytes[5]) shl 32 + + UInt64(FWsFrameHeader.Bytes[4]) shl 40 + + UInt64(FWsFrameHeader.Bytes[3]) shl 48 + + UInt64(FWsFrameHeader.Bytes[2]) shl 56 + ; + + // 接收完一帧 + if (FWsBodySize <= 0) then + begin + // 如果这是一个独立帧或者连续帧的最后一帧 + // 则表示一次请求数据接收完成 + if FWsFIN then + begin + FWsFrameState := wsDone; + Break; + // 否则继续接收下一帧 + end else + _ResetFrameHeader; + end; + end; + end; + + wsBody: + begin + LByte := PBuf^; + // 如果 MASK 状态为 1, 则将收到的数据与 mask key 做异或处理 + if FWsMask then + begin + LByte := LByte xor PByte(@FWsMaskKey)[FWsMaskKeyShift]; + FWsMaskKeyShift := (FWsMaskKeyShift + 1) mod 4; + end; + FWsRequestBody.Write(LByte, 1); + Dec(ALen); + Inc(PBuf); + Dec(FWsBodySize); + + // 接收完一帧 + if (FWsBodySize <= 0) then + begin + // 如果这是一个独立帧或者连续帧的最后一帧 + // 则表示一次请求数据接收完成 + if FWsFIN then + begin + FWsFrameState := wsDone; + Break; + // 否则继续接收下一帧 + end else + _ResetFrameHeader; + end; + end; + end; + end; + + // 一个完整的 WebSocket 数据帧接收完毕 + if (FWsFrameState = wsDone) then + begin + case FWsOpCode of + WS_OP_CLOSE: + begin + // 关闭帧 + // 收到关闭帧, 如果已经发送关闭帧, 直接关闭连接 + // 否则, 需要发送关闭帧, 发送完成之后关闭连接 + _RespondClose; + Exit; + end; + + WS_OP_PING: + begin + // pong 帧必须将 ping 帧发来的数据原封不动地返回 + LReqData := FWsRequestBody.Bytes; + SetLength(LReqData, FWsRequestBody.Size); + _RespondPong(LReqData); + end; + + WS_OP_TEXT, WS_OP_BINARY: + begin + // 收到请求数据 + LReqData := FWsRequestBody.Bytes; + SetLength(LReqData, FWsRequestBody.Size); + TriggerWsRequest(_OpCodeToReqType(FWsOpCode), LReqData); + end; + end; + + _ResetRequest; + end; + end; +end; + +procedure TCrossWebSocketConnection._RespondClose; +begin + if (AtomicExchange(FWsSendClose, 1) = 1) then + Disconnect + else + begin + _WsSend(WS_OP_CLOSE, True, nil, 0, + procedure(AConnection: ICrossWebSocketConnection; ASuccess: Boolean) + begin + AConnection.Disconnect; + end); + end; +end; + +procedure TCrossWebSocketConnection._RespondPong(const AData: TBytes); +begin + _WsSend(WS_OP_PONG, True, AData); +end; + +procedure TCrossWebSocketConnection._WsSend(AOpCode: Byte; AFin: Boolean; + AData: Pointer; ACount: NativeInt; + ACallback: TProc); +var + LWsFrameHeader: TBytes; +begin + LWsFrameHeader := _MakeFrameHeader(AOpCode, AFin, 0, ACount); + inherited SendBytes(LWsFrameHeader, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + if not ASuccess then + begin + if Assigned(ACallback) then + ACallback(AConnection as ICrossWebSocketConnection, ASuccess); + Exit; + end; + + if (AData = nil) or (ACount <= 0) then + begin + if Assigned(ACallback) then + ACallback(AConnection as ICrossWebSocketConnection, ASuccess); + Exit; + end; + + inherited SendBuf(AData^, ACount, + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + if Assigned(ACallback) then + ACallback(AConnection as ICrossWebSocketConnection, ASuccess); + end); + end); +end; + +procedure TCrossWebSocketConnection._WsSend(AOpCode: Byte; AFin: Boolean; + const AData: TBytes; AOffset, ACount: NativeInt; + ACallback: TProc); +var + LData: TBytes; + LOffset, LCount: NativeInt; +begin + LData := AData; + LOffset := AOffset; + LCount := ACount; + _AdjustOffsetCount(Length(AData), LOffset, LCount); + + _WsSend(AOpCode, AFin, @LData[LOffset], LCount, + procedure(AConnection: ICrossWebSocketConnection; ASuccess: Boolean) + begin + LData := nil; + if Assigned(ACallback) then + ACallback(AConnection as ICrossWebSocketConnection, ASuccess); + end); +end; + +procedure TCrossWebSocketConnection._WsSend(AOpCode: Byte; AFin: Boolean; + const AData: TBytes; ACallback: TProc); +begin + _WsSend(AOpCode, AFin, AData, 0, Length(AData), ACallback); +end; + +{ TNetCrossWebSocketServer } + +constructor TNetCrossWebSocketServer.Create(AIoThreads: Integer); +begin + inherited; + + FOnOpenEvents := TList.Create; + FOnMessageEvents := TList.Create; + FOnCloseEvents := TList.Create; +end; + +destructor TNetCrossWebSocketServer.Destroy; +begin + FreeAndNil(FOnOpenEvents); + FreeAndNil(FOnMessageEvents); + FreeAndNil(FOnCloseEvents); + + inherited; +end; + +procedure TNetCrossWebSocketServer.LogicDisconnected( + AConnection: ICrossConnection); +var + LConnection: ICrossWebSocketConnection; +begin + LConnection := AConnection as ICrossWebSocketConnection; + if LConnection.IsWebSocket then + _OnClose(LConnection) + else + inherited; +end; + +procedure TNetCrossWebSocketServer.LogicReceived(AConnection: ICrossConnection; + ABuf: Pointer; ALen: Integer); +var + LConnection: ICrossWebSocketConnection; +begin + LConnection := AConnection as ICrossWebSocketConnection; + if LConnection.IsWebSocket then + TCrossWebSocketConnection(LConnection)._WebSocketRecv(ABuf, ALen) + else + inherited; +end; + +function TNetCrossWebSocketServer.OnClose(ACallback: TWsOnClose): ICrossWebSocketServer; +begin + System.TMonitor.Enter(FOnCloseEvents); + try + FOnCloseEvents.Add(ACallback); + finally + System.TMonitor.Exit(FOnCloseEvents); + end; + + Result := Self; +end; + +function TNetCrossWebSocketServer.OnMessage(ACallback: TWsOnMessage): ICrossWebSocketServer; +begin + System.TMonitor.Enter(FOnMessageEvents); + try + FOnMessageEvents.Add(ACallback); + finally + System.TMonitor.Exit(FOnMessageEvents); + end; + + Result := Self; +end; + +function TNetCrossWebSocketServer.OnOpen(ACallback: TWsOnOpen): ICrossWebSocketServer; +begin + System.TMonitor.Enter(FOnOpenEvents); + try + FOnOpenEvents.Add(ACallback); + finally + System.TMonitor.Exit(FOnOpenEvents); + end; + + Result := Self; +end; + +procedure TNetCrossWebSocketServer.TriggerWsRequest( + AConnection: ICrossWebSocketConnection; ARequestType: TWsRequestType; + const ARequestData: TBytes); +begin + _OnMessage(AConnection, ARequestType, ARequestData); +end; + +procedure TNetCrossWebSocketServer._OnClose( + AConnection: ICrossWebSocketConnection); +var + LOnCloseEvents: TArray; + LOnCloseEvent: TWsOnClose; +begin + System.TMonitor.Enter(FOnCloseEvents); + try + LOnCloseEvents := FOnCloseEvents.ToArray; + finally + System.TMonitor.Exit(FOnCloseEvents); + end; + + for LOnCloseEvent in LOnCloseEvents do + if Assigned(LOnCloseEvent) then + LOnCloseEvent(AConnection); +end; + +procedure TNetCrossWebSocketServer._OnMessage( + AConnection: ICrossWebSocketConnection; ARequestType: TWsRequestType; + const ARequestData: TBytes); +var + LOnMessageEvents: TArray; + LOnMessageEvent: TWsOnMessage; +begin + System.TMonitor.Enter(FOnMessageEvents); + try + LOnMessageEvents := FOnMessageEvents.ToArray; + finally + System.TMonitor.Exit(FOnMessageEvents); + end; + + for LOnMessageEvent in LOnMessageEvents do + if Assigned(LOnMessageEvent) then + LOnMessageEvent(AConnection, ARequestType, ARequestData); +end; + +procedure TNetCrossWebSocketServer._OnOpen( + AConnection: ICrossWebSocketConnection); +var + LOnOpenEvents: TArray; + LOnOpenEvent: TWsOnOpen; +begin + System.TMonitor.Enter(FOnOpenEvents); + try + LOnOpenEvents := FOnOpenEvents.ToArray; + finally + System.TMonitor.Exit(FOnOpenEvents); + end; + + for LOnOpenEvent in LOnOpenEvents do + if Assigned(LOnOpenEvent) then + LOnOpenEvent(AConnection); +end; + +procedure TNetCrossWebSocketServer._WebSocketHandshake( + AConnection: ICrossWebSocketConnection; + ACallback: TProc); +begin + AConnection.Response.Header['Upgrade'] := 'websocket'; + AConnection.Response.Header['Connection'] := 'Upgrade'; + AConnection.Response.Header['Sec-WebSocket-Accept'] := + TNetEncoding.Base64.EncodeBytesToString( + THashSHA1.GetHashBytes( + AConnection.Request.Header['Sec-WebSocket-Key'] + WS_MAGIC_STR + ) + ); + AConnection.Response.SendStatus(101, '', + procedure(AConnection: ICrossConnection; ASuccess: Boolean) + begin + ACallback(AConnection as ICrossWebSocketConnection, ASuccess); + end); +end; + +function TNetCrossWebSocketServer.CreateConnection(AOwner: ICrossSocket; + AClientSocket: THandle; AConnectType: TConnectType): ICrossConnection; +begin + Result := TCrossWebSocketConnection.Create(AOwner, AClientSocket, AConnectType); +end; + +procedure TNetCrossWebSocketServer.DoOnRequest( + AConnection: ICrossHttpConnection); +var + LConnection: ICrossWebSocketConnection; +begin + LConnection := AConnection as ICrossWebSocketConnection; + if ContainsText(AConnection.Request.Header['Connection'], 'Upgrade') + and ContainsText(AConnection.Request.Header['Upgrade'], 'websocket') then + begin + TCrossWebSocketConnection(LConnection).FIsWebSocket := True; + _WebSocketHandshake(LConnection, + procedure(AConnection: ICrossWebSocketConnection; ASuccess: Boolean) + begin + if ASuccess then + _OnOpen(AConnection); + end); + end else + inherited; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.MbedBIO.pas b/ThirdParty/DCS/Net/Net.MbedBIO.pas new file mode 100644 index 00000000..2607ff65 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.MbedBIO.pas @@ -0,0 +1,390 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.MbedBIO; + +interface + +uses + System.SysUtils, + System.IOUtils, + Net.MbedTls; + +const + SSL_BIO_ERROR = -1; + SSL_BIO_UNSET = -2; + SSL_BIO_SIZE = 16384; // default BIO write size if not set + + SSL_FAILED = -1; + SSL_SUCCESS = 0; + + // BIO_TYPE + BIO_BUFFER = 1; + BIO_SOCKET = 2; + BIO_SSL = 3; + BIO_MEMORY = 4; + BIO_BIO = 5; + BIO_FILE = 6; + +type + PBIO = ^BIO; + + BIO = record + prev: PBIO; // previous in chain + next: PBIO; // next in chain + pair: PBIO; // BIO paired with + mem: PByte; // memory buffer + wrSz: Integer; // write buffer size (mem) + wrIdx: Integer; // current index for write buffer + rdIdx: Integer; // current read index + readRq: Integer; // read request + memLen: Integer; // memory buffer length + &type: Integer; // method type + end; + + // 抽象 IO API +function SSL_BIO_new(&type: Integer): PBIO; +function BIO_make_bio_pair(b1, b2: PBIO): Integer; + +function BIO_ctrl_pending(BIO: PBIO): Integer; +function BIO_set_write_buf_size(BIO: PBIO; size: Integer): Integer; + +function BIO_read(BIO: PBIO; buf: Pointer; size: Integer): Integer; +function BIO_write(BIO: PBIO; buf: Pointer; size: Integer): Integer; + +function BIO_free(BIO: PBIO): Integer; +function BIO_free_all(BIO: PBIO): Integer; + +function BIO_net_recv(ctx: Pointer; buf: Pointer; size: Size_T): Integer; cdecl; +function BIO_net_send(ctx: Pointer; buf: Pointer; size: Size_T): Integer; cdecl; + +implementation + +// Return the number of pending bytes in read and write buffers +function BIO_ctrl_pending(BIO: PBIO): Integer; +var + pair: PBIO; +begin + if (BIO = nil) then + Exit(0); + + if (BIO.&type = BIO_MEMORY) then + Exit(BIO.memLen); + + // type BIO_BIO then check paired buffer + if (BIO.&type = BIO_BIO) and (BIO.pair <> nil) then + begin + pair := BIO.pair; + if (pair.wrIdx > 0) and (pair.wrIdx <= pair.rdIdx) then + // in wrap around state where begining of buffer is being overwritten + Exit(pair.wrSz - pair.rdIdx + pair.wrIdx) + else + // simple case where has not wrapped around + Exit(pair.wrIdx - pair.rdIdx); + end; + + Result := 0; +end; + +function BIO_set_write_buf_size(BIO: PBIO; size: Integer): Integer; +begin + if (BIO = nil) or (BIO.&type <> BIO_BIO) or (size < 0) then + Exit(SSL_FAILED); + + // if already in pair then do not change size + if BIO.pair <> nil then + Exit(SSL_FAILED); + + BIO.wrSz := size; + if (BIO.wrSz < 0) then + Exit(SSL_FAILED); + + if (BIO.mem <> nil) then + FreeMem(BIO.mem); + + GetMem(BIO.mem, BIO.wrSz); + if (BIO.mem = nil) then + Exit(SSL_FAILED); + + BIO.wrIdx := 0; + BIO.rdIdx := 0; + Result := SSL_SUCCESS; +end; + +{ * Joins two BIO_BIO types. The write of b1 goes to the read of b2 and vise + * versa. Creating something similar to a two way pipe. + * Reading and writing between the two BIOs is not thread safe, they are + * expected to be used by the same thread. + * } +function BIO_make_bio_pair(b1, b2: PBIO): Integer; +begin + if (b1 = nil) or (b2 = nil) then + Exit(SSL_FAILED); + + // both are expected to be of type BIO and not already paired + if (b1.&type <> BIO_BIO) + or (b2.&type <> BIO_BIO) or (b1.pair <> nil) or (b2.pair <> nil) then + Exit(SSL_FAILED); + + // set default write size if not already set + if (b1.mem = nil) + and (BIO_set_write_buf_size(b1, SSL_BIO_SIZE) <> SSL_SUCCESS) then + Exit(SSL_FAILED); + + if (b2.mem = nil) + and (BIO_set_write_buf_size(b2, SSL_BIO_SIZE) <> SSL_SUCCESS) then + Exit(SSL_FAILED); + + b1.pair := b2; + b2.pair := b1; + Result := SSL_SUCCESS; +end; + +// Does not advance read index pointer +function BIO_nread0(BIO: PBIO; buf: PPointer): Integer; +var + pair: PBIO; +begin + if (BIO = nil) or (buf = nil) then + Exit(0); + + // if paired read from pair + if (BIO.pair <> nil) then + begin + pair := BIO.pair; + + // case where have wrapped around write buffer + buf^ := pair.mem + pair.rdIdx; + if (pair.wrIdx > 0) and (pair.rdIdx >= pair.wrIdx) then + Exit(pair.wrSz - pair.rdIdx) + else + Exit(pair.wrIdx - pair.rdIdx); + end; + + Result := 0; +end; + +function BIO_nread(BIO: PBIO; buf: PPointer; num: Integer): Integer; +var + sz: Integer; +begin + sz := SSL_BIO_UNSET; + if (BIO = nil) or (buf = nil) then + Exit(SSL_FAILED); + + if (BIO.pair <> nil) then + begin + // special case if asking to read 0 bytes + if (num = 0) then + begin + buf^ := BIO.pair.mem + BIO.pair.rdIdx; + Exit(0); + end; + + // get amount able to read and set buffer pointer + sz := BIO_nread0(BIO, buf); + if (sz = 0) then + Exit(SSL_BIO_ERROR); + + if (num < sz) then + sz := num; + + BIO.pair.rdIdx := BIO.pair.rdIdx + sz; + + // check if have read to the end of the buffer and need to reset + if (BIO.pair.rdIdx = BIO.pair.wrSz) then + begin + BIO.pair.rdIdx := 0; + if (BIO.pair.wrIdx = BIO.pair.wrSz) then + BIO.pair.wrIdx := 0; + end; + + // check if read up to write index, if so then reset indexs + if (BIO.pair.rdIdx = BIO.pair.wrIdx) then + begin + BIO.pair.rdIdx := 0; + BIO.pair.wrIdx := 0; + end; + end; + + Result := sz; +end; + +function BIO_nwrite(BIO: PBIO; buf: PPointer; num: Integer): Integer; +var + sz: Integer; +begin + sz := SSL_BIO_UNSET; + if (BIO = nil) or (buf = nil) then + Exit(0); + + if (BIO.pair <> nil) then + begin + if num = 0 then + begin + buf^ := BIO.mem + BIO.wrIdx; + Exit(0); + end; + if (BIO.wrIdx < BIO.rdIdx) then + // if wrapped around only write up to read index. In this case + // rdIdx is always greater then wrIdx so sz will not be negative. + sz := BIO.rdIdx - BIO.wrIdx + else if (BIO.rdIdx > 0) and (BIO.wrIdx = BIO.rdIdx) then + Exit(SSL_BIO_ERROR) // no more room to write + else + begin + // write index is past read index so write to end of buffer + sz := BIO.wrSz - BIO.wrIdx; + if (sz <= 0) then + begin + // either an error has occured with write index or it is at the + // end of the write buffer. + if (BIO.rdIdx = 0) then + // no more room, nothing has been read + Exit(SSL_BIO_ERROR); + + BIO.wrIdx := 0; + + // check case where read index is not at 0 + if (BIO.rdIdx > 0) then + sz := BIO.rdIdx // can write up to the read index + else + sz := BIO.wrSz; // no restriction other then buffer size + end; + end; + + if (num < sz) then + sz := num; + + buf^ := BIO.mem + BIO.wrIdx; + BIO.wrIdx := BIO.wrIdx + sz; + + // if at the end of the buffer and space for wrap around then set + // write index back to 0 + if (BIO.wrIdx = BIO.wrSz) and (BIO.rdIdx > 0) then + BIO.wrIdx := 0; + end; + + Result := sz; +end; + +// Reset BIO to initial state +function BIO_reset(BIO: PBIO): Integer; +begin + if (BIO = nil) then + // -1 is consistent failure even for FILE type + Exit(SSL_BIO_ERROR); + + case BIO.&type of + BIO_BIO: + begin + BIO.rdIdx := 0; + BIO.wrIdx := 0; + Exit(0); + end; + end; + + Result := SSL_BIO_ERROR; +end; + +function BIO_read(BIO: PBIO; buf: Pointer; size: Integer): Integer; +var + sz: Integer; + pt: PPointer; +begin + sz := BIO_nread(BIO, @pt, size); + if (sz > 0) then + Move(pt^, buf^, sz); + + Result := sz; +end; + +function BIO_write(BIO: PBIO; buf: Pointer; size: Integer): Integer; +var + sz: Integer; + data: Pointer; +begin + // internal function where arguments have already been sanity checked + sz := BIO_nwrite(BIO, @data, size); + + // test space for write + if (sz <= 0) then + Exit(sz); + + Move(buf^, data^, sz); + Result := sz; +end; + +// support bio type only +function SSL_BIO_new(&type: Integer): PBIO; +begin + GetMem(Result, SizeOf(BIO)); + FillChar(Result^, SizeOf(BIO), 0); + Result.&type := &type; +end; + +function BIO_free(BIO: PBIO): Integer; +begin + // unchain?, doesn't matter in goahead since from free all + if (BIO <> nil) then + begin + // remove from pair by setting the paired bios pair to nil + if (BIO.pair <> nil) then + BIO.pair.pair := nil; + + if (BIO.mem <> nil) then + FreeMem(BIO.mem); + + FreeMem(BIO); + end; + + Result := 0; +end; + +function BIO_free_all(BIO: PBIO): Integer; +var + next: PBIO; +begin + while (BIO <> nil) do + begin + next := BIO.next; + BIO_free(BIO); + BIO := next; + end; + + Result := 0; +end; + +function BIO_net_send(ctx: Pointer; buf: Pointer; size: Size_T): Integer; +var + BIO: PBIO; + sz: Integer; +begin + BIO := PBIO(ctx); + sz := BIO_write(BIO, buf, size); + if (sz <= 0) then + Exit(MBEDTLS_ERR_SSL_WANT_WRITE); + + Result := sz; +end; + +function BIO_net_recv(ctx: Pointer; buf: Pointer; size: Size_T): Integer; +var + BIO: PBIO; + sz: Integer; +begin + BIO := PBIO(ctx); + sz := BIO_read(BIO, buf, size); + if (sz <= 0) then + Exit(MBEDTLS_ERR_SSL_WANT_READ); + + Result := sz; +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.MbedTls.pas b/ThirdParty/DCS/Net/Net.MbedTls.pas new file mode 100644 index 00000000..168acc55 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.MbedTls.pas @@ -0,0 +1,1641 @@ +{ ****************************************************************************** } +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{ ****************************************************************************** } +unit Net.MbedTls; + +{ + MbedTls 库编译说明 + + > Windows + 用 C++ Builder 打开 mbedtls.cbproj 工程文件 + Win32编译时一定要设置 Use 'classic' Borland compiler 为 true + Win64编译的 Release 目标文件链接到 Delphi 中时, aes.o 会报告错误的文件格式 + 使用 Debug 版本的 aes.o 代替则不会报错, 测试了 C++ Builder 10.2.3 和 10.3 生成的 aes.o 文件均有该问题 + + > Linux iOS Android + 使用 Make 命令进行编译 +} + +interface + +uses +{$IFDEF MSWINDOWS} + Winapi.Windows, + System.Win.Crtl, +{$ENDIF} + System.SysUtils; + +// C 语言中枚举类型大小为 4 字节 +{$Z4} + +const +{$IF defined(WIN32)} + _PU = '_'; +{$ELSE} + _PU = ''; +{$ENDIF} + +{$IF defined(WIN32)} + {$DEFINE __HAS_MBED_TLS_OBJ__} +{$ELSEIF defined(WIN64)} + {$DEFINE __HAS_MBED_TLS_O__} +{$ELSE} // POSIX + {$DEFINE __HAS_MBED_TLS_LIB__} +{$ENDIF} + +{$IFDEF __HAS_MBED_TLS_LIB__} + {$IF defined(IOS) or defined(ANDROID)} + LIB_MBED_CRYPTO = 'libmbedtls.a'; + LIB_MBED_TLS = 'libmbedtls.a'; + LIB_MBED_X509 = 'libmbedtls.a'; + {$ELSEIF defined(OSX)} + LIB_MBED_CRYPTO = 'libmbedcrypto.dylib'; + LIB_MBED_TLS = 'libmbedtls.dylib'; + LIB_MBED_X509 = 'libmbedx509.dylib'; + {$ELSE} // LINUX + LIB_MBED_CRYPTO = 'libmbedcrypto.so'; + LIB_MBED_TLS = 'libmbedtls.so'; + LIB_MBED_X509 = 'libmbedx509.so'; + {$ENDIF} +{$ENDIF} + +{$REGION 'MbedTls定义'} +const + MBEDTLS_SSL_VERIFY_DATA_MAX_LEN = 36; + MBEDTLS_ENTROPY_MAX_SOURCES = 20; // *< Maximum number of sources supported + MBEDTLS_HAVEGE_COLLECT_SIZE = 1024; + DEBUG_LEVEL = 0; + + MBEDTLS_TLS_RSA_WITH_NULL_MD5 = $01; + MBEDTLS_TLS_RSA_WITH_NULL_SHA = $02; + MBEDTLS_TLS_RSA_WITH_RC4_128_MD5 = $04; + MBEDTLS_TLS_RSA_WITH_RC4_128_SHA = $05; + MBEDTLS_TLS_RSA_WITH_DES_CBC_SHA = $09; + MBEDTLS_TLS_RSA_WITH_3DES_EDE_CBC_SHA = $0A; + MBEDTLS_TLS_DHE_RSA_WITH_DES_CBC_SHA = $15; + MBEDTLS_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = $16; + MBEDTLS_TLS_PSK_WITH_NULL_SHA = $2C; + MBEDTLS_TLS_DHE_PSK_WITH_NULL_SHA = $2D; + MBEDTLS_TLS_RSA_PSK_WITH_NULL_SHA = $2E; + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA = $2F; + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = $33; + MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA = $35; + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = $39; + MBEDTLS_TLS_RSA_WITH_NULL_SHA256 = $3B; + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256 = $3C; + MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA256 = $3D; + MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = $41; + MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = $45; + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = $67; + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = $6B; + MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = $84; + MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = $88; + MBEDTLS_TLS_PSK_WITH_RC4_128_SHA = $8A; + MBEDTLS_TLS_PSK_WITH_3DES_EDE_CBC_SHA = $8B; + MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA = $8C; + MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA = $8D; + MBEDTLS_TLS_DHE_PSK_WITH_RC4_128_SHA = $8E; + MBEDTLS_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = $8F; + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA = $90; + MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA = $91; + MBEDTLS_TLS_RSA_PSK_WITH_RC4_128_SHA = $92; + MBEDTLS_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = $93; + MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA = $94; + MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA = $95; + MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256 = $9C; + MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384 = $9D; + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = $9E; + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = $9F; + MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256 = $A8; + MBEDTLS_TLS_PSK_WITH_AES_256_GCM_SHA384 = $A9; + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = $AA; + MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = $AB; + MBEDTLS_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = $AC; + MBEDTLS_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = $AD; + MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA256 = $AE; + MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA384 = $AF; + MBEDTLS_TLS_PSK_WITH_NULL_SHA256 = $B0; + MBEDTLS_TLS_PSK_WITH_NULL_SHA384 = $B1; + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = $B2; + MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = $B3; + MBEDTLS_TLS_DHE_PSK_WITH_NULL_SHA256 = $B4; + MBEDTLS_TLS_DHE_PSK_WITH_NULL_SHA384 = $B5; + MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = $B6; + MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = $B7; + MBEDTLS_TLS_RSA_PSK_WITH_NULL_SHA256 = $B8; + MBEDTLS_TLS_RSA_PSK_WITH_NULL_SHA384 = $B9; + MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = $BA; + MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = $BE; + MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = $C0; + MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = $C4; + MBEDTLS_TLS_ECDH_ECDSA_WITH_NULL_SHA = $C001; + MBEDTLS_TLS_ECDH_ECDSA_WITH_RC4_128_SHA = $C002; + MBEDTLS_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = $C003; + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = $C004; + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = $C005; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_NULL_SHA = $C006; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = $C007; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = $C008; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = $C009; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = $C00A; + MBEDTLS_TLS_ECDH_RSA_WITH_NULL_SHA = $C00B; + MBEDTLS_TLS_ECDH_RSA_WITH_RC4_128_SHA = $C00C; + MBEDTLS_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = $C00D; + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = $C00E; + MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = $C00F; + MBEDTLS_TLS_ECDHE_RSA_WITH_NULL_SHA = $C010; + MBEDTLS_TLS_ECDHE_RSA_WITH_RC4_128_SHA = $C011; + MBEDTLS_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = $C012; + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = $C013; + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = $C014; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = $C023; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = $C024; + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = $C025; + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = $C026; + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = $C027; + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = $C028; + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = $C029; + MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = $C02A; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = $C02B; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = $C02C; + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = $C02D; + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = $C02E; + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = $C02F; + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = $C030; + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = $C031; + MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = $C032; + MBEDTLS_TLS_ECDHE_PSK_WITH_RC4_128_SHA = $C033; + MBEDTLS_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = $C034; + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = $C035; + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = $C036; + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = $C037; + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = $C038; + MBEDTLS_TLS_ECDHE_PSK_WITH_NULL_SHA = $C039; + MBEDTLS_TLS_ECDHE_PSK_WITH_NULL_SHA256 = $C03A; + MBEDTLS_TLS_ECDHE_PSK_WITH_NULL_SHA384 = $C03B; + MBEDTLS_TLS_RSA_WITH_ARIA_128_CBC_SHA256 = $C03C; + MBEDTLS_TLS_RSA_WITH_ARIA_256_CBC_SHA384 = $C03D; + MBEDTLS_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 = $C044; + MBEDTLS_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 = $C045; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 = $C048; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 = $C049; + MBEDTLS_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 = $C04A; + MBEDTLS_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 = $C04B; + MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 = $C04C; + MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 = $C04D; + MBEDTLS_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 = $C04E; + MBEDTLS_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 = $C04F; + MBEDTLS_TLS_RSA_WITH_ARIA_128_GCM_SHA256 = $C050; + MBEDTLS_TLS_RSA_WITH_ARIA_256_GCM_SHA384 = $C051; + MBEDTLS_TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 = $C052; + MBEDTLS_TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 = $C053; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 = $C05C; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 = $C05D; + MBEDTLS_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 = $C05E; + MBEDTLS_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 = $C05F; + MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 = $C060; + MBEDTLS_TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 = $C061; + MBEDTLS_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 = $C062; + MBEDTLS_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 = $C063; + MBEDTLS_TLS_PSK_WITH_ARIA_128_CBC_SHA256 = $C064; + MBEDTLS_TLS_PSK_WITH_ARIA_256_CBC_SHA384 = $C065; + MBEDTLS_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 = $C066; + MBEDTLS_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 = $C067; + MBEDTLS_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 = $C068; + MBEDTLS_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 = $C069; + MBEDTLS_TLS_PSK_WITH_ARIA_128_GCM_SHA256 = $C06A; + MBEDTLS_TLS_PSK_WITH_ARIA_256_GCM_SHA384 = $C06B; + MBEDTLS_TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 = $C06C; + MBEDTLS_TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 = $C06D; + MBEDTLS_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 = $C06E; + MBEDTLS_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 = $C06F; + MBEDTLS_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 = $C070; + MBEDTLS_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 = $C071; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = $C072; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = $C073; + MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = $C074; + MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = $C075; + MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = $C076; + MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = $C077; + MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = $C078; + MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = $C079; + MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = $C07A; + MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = $C07B; + MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = $C07C; + MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = $C07D; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = $C086; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = $C087; + MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = $C088; + MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = $C089; + MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = $C08A; + MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = $C08B; + MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = $C08C; + MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = $C08D; + MBEDTLS_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 = $C08E; + MBEDTLS_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 = $C08F; + MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 = $C090; + MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 = $C091; + MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 = $C092; + MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 = $C093; + MBEDTLS_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 = $C094; + MBEDTLS_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 = $C095; + MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = $C096; + MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = $C097; + MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 = $C098; + MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 = $C099; + MBEDTLS_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = $C09A; + MBEDTLS_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = $C09B; + MBEDTLS_TLS_RSA_WITH_AES_128_CCM = $C09C; + MBEDTLS_TLS_RSA_WITH_AES_256_CCM = $C09D; + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CCM = $C09E; + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CCM = $C09F; + MBEDTLS_TLS_RSA_WITH_AES_128_CCM_8 = $C0A0; + MBEDTLS_TLS_RSA_WITH_AES_256_CCM_8 = $C0A1; + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CCM_8 = $C0A2; + MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CCM_8 = $C0A3; + MBEDTLS_TLS_PSK_WITH_AES_128_CCM = $C0A4; + MBEDTLS_TLS_PSK_WITH_AES_256_CCM = $C0A5; + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CCM = $C0A6; + MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CCM = $C0A7; + MBEDTLS_TLS_PSK_WITH_AES_128_CCM_8 = $C0A8; + MBEDTLS_TLS_PSK_WITH_AES_256_CCM_8 = $C0A9; + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CCM_8 = $C0AA; + MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CCM_8 = $C0AB; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM = $C0AC; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CCM = $C0AD; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = $C0AE; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = $C0AF; + MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 = $C0FF; + MBEDTLS_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = $CCA8; + MBEDTLS_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = $CCA9; + MBEDTLS_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = $CCAA; + MBEDTLS_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = $CCAB; + MBEDTLS_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = $CCAC; + MBEDTLS_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = $CCAD; + MBEDTLS_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = $CCAE; + +type + TMbedtls_SSL_States = Integer; + // 定义完整的 TMbedtls_SSL_States 枚举类型在编译 Win64 执行文件时会报错 + // [dcc64 Error] Net.MbedTls.pas(1638): E2068 Illegal reference to symbol 'MBEDTLS_SSL_HANDSHAKE_WRAPUP' in object file 'D:\Design\Delphi\VclFmxRtl\zLib\Net\MbedTls\Win64\Release\ssl_cli.o' + // 这显然是编译器的问题, 经测试 Delphi 10.2.3 和 Delphi 10.3 均有该问题 +// TMbedtls_SSL_States = ( +// MBEDTLS_SSL_HELLO_REQUEST = 0, +// MBEDTLS_SSL_CLIENT_HELLO, +// MBEDTLS_SSL_SERVER_HELLO, +// MBEDTLS_SSL_SERVER_CERTIFICATE, +// MBEDTLS_SSL_SERVER_KEY_EXCHANGE, +// MBEDTLS_SSL_CERTIFICATE_REQUEST, +// MBEDTLS_SSL_SERVER_HELLO_DONE, +// MBEDTLS_SSL_CLIENT_CERTIFICATE, +// MBEDTLS_SSL_CLIENT_KEY_EXCHANGE, +// MBEDTLS_SSL_CERTIFICATE_VERIFY, +// MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC, +// MBEDTLS_SSL_CLIENT_FINISHED, +// MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC, +// MBEDTLS_SSL_SERVER_FINISHED, +// MBEDTLS_SSL_FLUSH_BUFFERS, +// MBEDTLS_SSL_HANDSHAKE_WRAPUP, +// MBEDTLS_SSL_HANDSHAKE_OVER, +// MBEDTLS_SSL_SERVER_NEW_SESSION_TICKET, +// MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT +// ); + + Size_T = NativeUInt; + PSize_T = ^Size_T; + UInt32_T = UInt32; + PUint32_T = ^UInt32_T; + UInt16_T = UInt16; + UInt64_T = UInt64; + UInt8_T = UInt8; + + PMbedtls_SSL_Session = Pointer; + PMbedtls_SSL_Transform = Pointer; + PMbedtls_SSL_Handshake_Params = Pointer; + PMbedtls_MD_Info = Pointer; // opaque + PMbedtls_X509_Crl = Pointer; + PMbedtls_Cipher_Info = Pointer; + + PMbedtls_SSL_Send = Pointer; + PMbedtls_SSL_Recv = Pointer; + PMbedtls_SSL_Recv_Timeout = Pointer; + PMbedtls_SSL_Set_Timer = Pointer; + PMbedtls_SSL_Get_Timer = Pointer; + + PMbedtls_SSL_Cache_Entry = Pointer; + + PTlsMutex = ^TTlsMutex; + TTlsMutex = record + Lock: TObject; + IsValid: Byte; + end; + + TMbedtls_MPI = record + s: Integer; // integer sign + n: Size_T; // total # of limbs + p: Pointer; // pointer to limbs + end; + + // + // SSL/TLS configuration to be shared between mbedtls_ssl_context structures. + // + PMbedtls_SSL_Config = ^TMbedtls_SSL_Config; + TMbedtls_SSL_Config = record + // Group items by size (largest first) to minimize padding overhead + + // + // Pointers + // + + ciphersuite_list: array [0 .. 3] of PInteger; + // allowed ciphersuites per version + + // Callback for printing debug output + f_dbg: Pointer; + p_dbg: Pointer; // !< context for the debug function + + // ** Callback for getting (pseudo-)random numbers + f_rng: Pointer; + p_rng: Pointer; // !< context for the RNG function + + // ** Callback to retrieve a session from the cache + f_get_cache: Pointer; + // ** Callback to store a session into the cache + f_set_cache: Pointer; + p_cache: Pointer; // !< context for cache callbacks + + // ** Callback for setting cert according to SNI extension + f_sni: Pointer; + p_sni: Pointer; // !< context for SNI callback + + // ** Callback to customize X.509 certificate chain verification + f_vrfy: Pointer; + p_vrfy: Pointer; // !< context for X.509 verify calllback + + // ** Callback to retrieve PSK key from identity + f_psk: Pointer; + p_psk: Pointer; // !< context for PSK callback + + // ** Callback to create & write a cookie for ClientHello veirifcation + f_cookie_write: Pointer; + // ** Callback to verify validity of a ClientHello cookie + f_cookie_check: Pointer; + p_cookie: Pointer; // !< context for the cookie callbacks + + // ** Callback to create & write a session ticket + f_ticket_write: Pointer; + // ** Callback to parse a session ticket into a session structure + f_ticket_parse: Pointer; + p_ticket: Pointer; // !< context for the ticket callbacks + + // ** Callback to export key block and master secret + f_export_keys: Pointer; + p_export_keys: Pointer; // !< context for key export callback + + cert_profile: Pointer; // !< verification profile + key_cert: Pointer; // !< own certificate/key pair(s) + ca_chain: Pointer; // !< trusted CAs + ca_crl: Pointer; // !< trusted CAs CRLs + + f_async_sign_start: Pointer; // !< start asynchronous signature operation + f_async_decrypt_start: Pointer; // !< start asynchronous decryption operation + f_async_resume: Pointer; // !< resume asynchronous operation + f_async_cancel: Pointer; // !< cancel asynchronous operation + p_async_config_data: Pointer; // !< Configuration data set by mbedtls_ssl_conf_async_private_cb(). + + sig_hashes: PInteger; // !< allowed signature hashes + + curve_list: Pointer; // !< allowed curves + + dhm_P: TMbedtls_MPI; // !< prime modulus for DHM + dhm_G: TMbedtls_MPI; // !< generator for DHM + + psk: PByte; // !< pre-shared key. This field should + // only be set via + // mbedtls_ssl_conf_psk() + psk_len: Size_T; // !< length of the pre-shared key. This + // field should only be set via + // mbedtls_ssl_conf_psk() + psk_identity: PByte; // !< identity for PSK negotiation. This + // field should only be set via + // mbedtls_ssl_conf_psk() + psk_identity_len: Size_T; // !< length of identity. This field should + // only be set via + // mbedtls_ssl_conf_psk() + + alpn_list: PMarshaledAString; // !< ordered list of protocols + + // + // * Numerical settings (int then char) + // + + read_timeout: UInt32_T; // !< timeout for mbedtls_ssl_read (ms) + + hs_timeout_min: UInt32_T; // !< initial value of the handshake + // retransmission timeout (ms) + hs_timeout_max: UInt32_T; // !< maximum value of the handshake + // retransmission timeout (ms) + + renego_max_records: Integer; // !< grace period for renegotiation + renego_period: array [0 .. 7] of UInt8_T; // !< value of the record counters + // that triggers renegotiation + + badmac_limit: UInt32_T; // !< limit of records with a bad MAC + + dhm_min_bitlen: UInt32_T; // !< min. bit length of the DHM prime + + max_major_ver: UInt8_T; // !< max. major version used + max_minor_ver: UInt8_T; // !< max. minor version used + min_major_ver: UInt8_T; // !< min. major version used + min_minor_ver: UInt8_T; // !< min. minor version used + + // + // * Flags (bitfields) + // + Flag: UInt32_T; + end; + + PMbedtls_SSL_Context = ^TMbedtls_SSL_Context; + TMbedtls_SSL_Context = record + conf: PMbedtls_SSL_Config; // !< configuration information + + // + // * Miscellaneous + // + state: Integer; // !< SSL handshake: current state + renego_status: Integer; // !< Initial, in progress, pending? + renego_records_seen: Integer; // !< Records since renego request, or with DTLS, + // number of retransmissions of request if + // renego_max_records is < 0 + + major_ver: Integer; // !< equal to MBEDTLS_SSL_MAJOR_VERSION_3 + minor_ver: Integer; // !< either 0 (SSL3) or 1 (TLS1.0) + + badmac_seen: Cardinal; // !< records with a bad MAC received + + f_send: PMbedtls_SSL_Send; // !< Callback for network send + f_recv: PMbedtls_SSL_Recv; // !< Callback for network receive + f_recv_timeout: PMbedtls_SSL_Recv_Timeout; // !< Callback for network receive with timeout + + p_bio: Pointer; // !< context for I/O operations + + // + // * Session layer + // + session_in: PMbedtls_SSL_Session; // !< current session data (in) + session_out: PMbedtls_SSL_Session; // !< current session data (out) + session: PMbedtls_SSL_Session; // !< negotiated session data + session_negotiate: PMbedtls_SSL_Session; // !< session data in negotiation + + handshake: PMbedtls_SSL_Handshake_Params; // !< params required only during + // the handshake process + + // + // * Record layer transformations + // + transform_in: PMbedtls_SSL_Transform; // !< current transform params (in) + transform_out: PMbedtls_SSL_Transform; // !< current transform params (in) + transform: PMbedtls_SSL_Transform; // !< negotiated transform params + transform_negotiate: PMbedtls_SSL_Transform; // !< transform params in negotiation + + // + // * Timers + // + p_timer: Pointer; // !< context for the timer callbacks + + f_set_timer: PMbedtls_SSL_Set_Timer; // !< set timer callback + f_get_timer: PMbedtls_SSL_Get_Timer; // !< get timer callback + + // + // * Record layer (incoming data) + // + in_buf: PByte; // !< input buffer + in_ctr: PByte; // !< 64-bit incoming message counter + // TLS: maintained by us + // DTLS: read from peer + in_hdr: PByte; // !< start of record header + in_len: PByte; // !< two-bytes message length field + in_iv: PByte; // !< ivlen-byte IV + in_msg: PByte; // !< message contents (in_iv+ivlen) + in_offt: PByte; // !< read offset in application data + + in_msgtype: Integer; // !< record header: message type + in_msglen: Size_T; // !< record header: message length + in_left: Size_T; // !< amount of data read so far + in_epoch: UInt16_T; // !< DTLS epoch for incoming records + next_record_offset: Size_T; // !< offset of the next record in datagram + // (equal to in_left if none) + in_window_top: UInt64_T; // !< last validated record seq_num + in_window: UInt64_T; // !< bitmask for replay detection + + in_hslen: Size_T; // !< current handshake message length, + // including the handshake header + nb_zero: Integer; // !< # of 0-length encrypted messages + + keep_current_message: Integer; // !< drop or reuse current message + // on next call to record layer? + + disable_datagram_packing: UInt8_T; // !< Disable packing multiple records + // within a single datagram. + + // + // * Record layer (outgoing data) + // + out_buf: PByte; // !< output buffer + out_ctr: PByte; // !< 64-bit outgoing message counter + out_hdr: PByte; // !< start of record header + out_len: PByte; // !< two-bytes message length field + out_iv: PByte; // !< ivlen-byte IV + out_msg: PByte; // !< message contents (out_iv+ivlen) + + out_msgtype: Integer; // !< record header: message type + out_msglen: Size_T; // !< record header: message length + out_left: Size_T; // !< amount of data not yet written + + cur_out_ctr: array [0 .. 7] of Byte; // !< Outgoing record sequence number. + + mtu: UInt16_T; // !< path mtu, used to fragment outgoing messages + + compress_buf: PByte; // !< zlib data buffer + split_done: Byte; // !< current record already splitted? + + // + // * PKI layer + // + client_auth: Integer; // !< flag for client auth. + + // + // * User settings + // + hostname: MarshaledAString; // !< expected peer CN for verification + // (and SNI if available) + + alpn_chosen: MarshaledAString; // !< negotiated protocol + + // + // * Information for DTLS hello verify + // + cli_id: PByte; // !< transport-level ID of the client + cli_id_len: Size_T; // !< length of cli_id + + // + // * Secure renegotiation + // + // needed to know when to send extension on server + secure_renegotiation: Integer; // !< does peer support legacy or + // secure renegotiation + verify_data_len: Size_T; // !< length of verify data stored + own_verify_data: array [0 .. MBEDTLS_SSL_VERIFY_DATA_MAX_LEN - 1] of Byte; // !< previous handshake verify data + peer_verify_data: array [0 .. MBEDTLS_SSL_VERIFY_DATA_MAX_LEN - 1] of Byte; // !< previous handshake verify data + end; + + PMbedtls_SSL_Cache_Context = ^TMbedtls_SSL_Cache_Context; + TMbedtls_SSL_Cache_Context = record + chain: PMbedtls_SSL_Cache_Entry; // !< start of the chain + timeout: Integer; // !< cache entry timeout + max_entries: Integer; // !< maximum entries + mutex: TTlsMutex; // !< mutex + end; + + TMbedtls_Asn1_Buf = record + tag: Integer; // **< ASN1 type, e.g. MBEDTLS_ASN1_UTF8_STRING. + len: Size_T; // **< ASN1 length, in octets. + p: PByte; // **< ASN1 data, e.g. in ASCII. + end; + + TMbedtls_X509_Buf = TMbedtls_Asn1_Buf; + + PMbedtls_Asn1_Named_Data = ^TMbedtls_Asn1_Named_Data; + TMbedtls_Asn1_Named_Data = record + oid: TMbedtls_Asn1_Buf; // **< The object identifier. + val: TMbedtls_Asn1_Buf; // **< The named value. + next: PMbedtls_Asn1_Named_Data; // **< The next entry in the sequence. + next_merged: Byte; // **< Merge next item into the current one? + end; + + TMbedtls_X509_Name = TMbedtls_Asn1_Named_Data; + + TMbedtls_X509_Time = record + year, mon, day: Integer; // **< Date. + hour, min, sec: Integer; // **< Time. + end; + + TMbedtls_PK_Type = ( + MBEDTLS_PK_NONE = 0, + MBEDTLS_PK_RSA, + MBEDTLS_PK_ECKEY, + MBEDTLS_PK_ECKEY_DH, + MBEDTLS_PK_ECDSA, + MBEDTLS_PK_RSA_ALT, + MBEDTLS_PK_RSASSA_PSS + ); + + PMbedtls_PK_Info = ^TMbedtls_PK_Info; + TMbedtls_PK_Info = record + // ** Public key type + &type: TMbedtls_PK_Type; + + // ** Type name + name: MarshaledAString; + + // ** Get key size in bits + get_bitlen: Pointer; + + // ** Tell if the context implements this type (e.g. ECKEY can do ECDSA) + can_do: Pointer; + + // ** Verify signature + verify_func: Pointer; + + // ** Make signature + sign_func: Pointer; + + // ** Decrypt message + decrypt_func: Pointer; + + // ** Encrypt message + encrypt_func: Pointer; + + // ** Check public-private key pair + check_pair_func: Pointer; + + // ** Allocate a new context + ctx_alloc_func: Pointer; + + // ** Free the given context + ctx_free_func: Pointer; + + // ** Interface with the debug module + debug_func: Pointer; + end; + + PMbedtls_PK_Context = ^TMbedtls_PK_Context; + TMbedtls_PK_Context = record + pk_info: PMbedtls_PK_Info; // **< Public key informations + pk_ctx: Pointer; // **< Underlying public key context + end; + + PMbedtls_Asn1_Sequence = ^TMbedtls_Asn1_Sequence; + TMbedtls_Asn1_Sequence = record + buf: TMbedtls_Asn1_Buf; // **< Buffer containing the given ASN.1 item. + next: PMbedtls_Asn1_Sequence; // **< The next entry in the sequence. + end; + + TMbedtls_X509_Sequence = TMbedtls_Asn1_Sequence; + + TMbedtls_MD_Type = ( + MBEDTLS_MD_NONE = 0, // **< None. + MBEDTLS_MD_MD2, // **< The MD2 message digest. + MBEDTLS_MD_MD4, // **< The MD4 message digest. + MBEDTLS_MD_MD5, // **< The MD5 message digest. + MBEDTLS_MD_SHA1, // **< The SHA-1 message digest. + MBEDTLS_MD_SHA224, // **< The SHA-224 message digest. + MBEDTLS_MD_SHA256, // **< The SHA-256 message digest. + MBEDTLS_MD_SHA384, // **< The SHA-384 message digest. + MBEDTLS_MD_SHA512, // **< The SHA-512 message digest. + MBEDTLS_MD_RIPEMD160 // **< The RIPEMD-160 message digest. + ); + + PMbedtls_X509_CRT = ^TMbedtls_X509_CRT; + TMbedtls_X509_CRT = record + raw: TMbedtls_X509_Buf; // **< The raw certificate data (DER). + tbs: TMbedtls_X509_Buf; // **< The raw certificate body (DER). The part that is To Be Signed. + + version: Integer; // **< The X.509 version. (1=v1, 2=v2, 3=v3) + serial: TMbedtls_X509_Buf; // **< Unique id for certificate issued by a specific CA. + sig_oid: TMbedtls_X509_Buf; // **< Signature algorithm, e.g. sha1RSA + + issuer_raw: TMbedtls_X509_Buf; // **< The raw issuer data (DER). Used for quick comparison. + subject_raw: TMbedtls_X509_Buf; // **< The raw subject data (DER). Used for quick comparison. + + issuer: TMbedtls_X509_Name; // **< The parsed issuer data (named information object). + subject: TMbedtls_X509_Name; // **< The parsed subject data (named information object). + + valid_from: TMbedtls_X509_Time; // **< Start time of certificate validity. + valid_to: TMbedtls_X509_Time; // **< End time of certificate validity. + + pk: TMbedtls_PK_Context; // **< Container for the public key context. + + issuer_id: TMbedtls_X509_Buf; // **< Optional X.509 v2/v3 issuer unique identifier. + subject_id: TMbedtls_X509_Buf; // **< Optional X.509 v2/v3 subject unique identifier. + v3_ext: TMbedtls_X509_Buf; // **< Optional X.509 v3 extensions. + subject_alt_names: TMbedtls_X509_Sequence; // **< Optional list of Subject Alternative Names (Only dNSName supported). + + ext_types: Integer; // **< Bit string containing detected and parsed extensions + ca_istrue: Integer; // **< Optional Basic Constraint extension value: 1 if this certificate belongs to a CA, 0 otherwise. + max_pathlen: Integer; // **< Optional Basic Constraint extension value: The maximum path length to the root certificate. Path length is 1 higher than RFC 5280 'meaning', so 1+ + + key_usage: UInt32_T; // **< Optional key usage extension value: See the values in x509.h + + ext_key_usage: TMbedtls_X509_Sequence; // **< Optional list of extended key usage OIDs. + + ns_cert_type: Byte; // **< Optional Netscape certificate type extension value: See the values in x509.h + + sig: TMbedtls_X509_Buf; // **< Signature: hash of the tbs part signed with the private key. + sig_md: TMbedtls_MD_Type; // **< Internal representation of the MD algorithm of the signature algorithm, e.g. MBEDTLS_MD_SHA256 + sig_pk: TMbedtls_PK_Type; // **< Internal representation of the Public Key algorithm of the signature algorithm, e.g. MBEDTLS_PK_RSA + sig_opts: Pointer; // **< Signature options to be passed to mbedtls_pk_verify_ext(), e.g. for RSASSA-PSS + + next: PMbedtls_X509_CRT; // **< Next certificate in the CA-chain. + end; + + TMbedtls_SHA512_Context = record + total: array [0 .. 1] of UInt64_T; // !< The number of Bytes processed. + state: array [0 .. 7] of UInt64_T; // !< The intermediate digest state. + buffer: array [0 .. 127] of Byte; // !< The data block being processed. + is384: Integer; // !< Determines which function to use: + // 0: Use SHA-512, or 1: Use SHA-384. + end; + + TMbedtls_Entropy_Source_State = record + f_source: Pointer; // **< The entropy source callback + p_source: Pointer; // **< The callback data pointer + size: Size_T; // **< Amount received in bytes + threshold: Size_T; // **< Minimum bytes required before release + strong: Integer; // **< Is the source strong? + end; + + TMbedtls_Havege_State = record + PT1: Integer; + PT2: Integer; + offset: array [0 .. 1] of Integer; + pool: array [0 .. MBEDTLS_HAVEGE_COLLECT_SIZE - 1] of Integer; + WALK: array [0 .. 8191] of Integer; + end; + + PMbedtls_Entropy_Context = ^TMbedtls_Entropy_Context; + TMbedtls_Entropy_Context = record + accumulator_started: Integer; + accumulator: TMbedtls_SHA512_Context; + source_count: Integer; + source: array [0 .. MBEDTLS_ENTROPY_MAX_SOURCES - 1] of TMbedtls_Entropy_Source_State; + havege_data: TMbedtls_Havege_State; + mutex: TTlsMutex; // !< mutex + initial_entropy_run: Integer; + end; + + TMbedtls_AES_Context = record + nr: Integer; // !< The number of rounds. + rk: PUint32_T; // !< AES round keys. + buf: array [0 .. 67] of UInt32_T; // !< Unaligned data buffer. This buffer can + // hold 32 extra Bytes, which can be used for + // one of the following purposes: + //
  • Alignment if VIA padlock is + // used.
  • + //
  • Simplifying key expansion in the 256-bit + // case by generating an extra round key. + //
+ end; + + PMbedtls_CTR_DRBG_Context = ^TMbedtls_CTR_DRBG_Context; + TMbedtls_CTR_DRBG_Context = record + counter: array [0 .. 15] of Byte; // !< The counter (V). + reseed_counter: Integer; // !< The reseed counter. + prediction_resistance: Integer; // !< This determines whether prediction + // resistance is enabled, that is + // whether to systematically reseed before + // each random generation. + entropy_len: Size_T; // !< The amount of entropy grabbed on each + // seed or reseed operation. + reseed_interval: Integer; // !< The reseed interval. + + aes_ctx: TMbedtls_AES_Context; // !< The AES context. + + // + // * Callbacks (Entropy) + // + f_entropy: Pointer; // !< The entropy callback function. + + p_entropy: Pointer; // !< The context for the entropy function. + + mutex: TTlsMutex; + end; + + TEntropyFunc = function(data: Pointer; output: MarshaledAString; len: Size_T): Integer; cdecl; + PEntropyFunc = ^TEntropyFunc; + TrngFunc = function(data: Pointer; output: MarshaledAString; len: Size_T): Integer; cdecl; + TdbgFunc = procedure(data: Pointer; i: Integer; c: MarshaledAString; i2: Integer; c2: MarshaledAString); cdecl; + TNetSendFunc = function(ctx: Pointer; buf: Pointer; len: Size_T): Integer; cdecl; + TNetRecvFunc = function(ctx: Pointer; buf: Pointer; len: Size_T): Integer; cdecl; + TNetRecvTimeoutFunc = function(ctx: Pointer; buf: Pointer; len: Size_T; timeout: UInt32_T): Integer; cdecl; + TGetTimerFunc = function(ctx: Pointer): Integer; cdecl; + TSetTimerFunc = procedure(ctx: Pointer; int_ms: UInt32_T; fin_ms: UInt32_T); cdecl; + + TGetCacheFunc = function(data: Pointer; session: PMbedtls_SSL_Session): Integer; cdecl; + TSetCacheFunc = function(data: Pointer; const session: PMbedtls_SSL_Session): Integer; cdecl; + +const + // SSL Error codes - actually negative of these + MBEDTLS_ERR_MPI_FILE_IO_ERROR = -$0002; // An error occurred while reading from or writing to a file. + MBEDTLS_ERR_MPI_BAD_INPUT_DATA = -$0004; // Bad input parameters to function. + MBEDTLS_ERR_MPI_INVALID_CHARACTER = -$0006; // There is an invalid character in the digit string. + MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL = -$0008; // The buffer is too small to write to. + MBEDTLS_ERR_MPI_NEGATIVE_VALUE = -$000A; // The input arguments are negative or result in illegal output. + MBEDTLS_ERR_MPI_DIVISION_BY_ZERO = -$000C; // The input argument for division is zero, which is not allowed. + MBEDTLS_ERR_MPI_NOT_ACCEPTABLE = -$000E; // The input arguments are not acceptable. + MBEDTLS_ERR_MPI_ALLOC_FAILED = -$0010; // Memory allocation failed. + + MBEDTLS_ERR_HMAC_DRBG_REQUEST_TOO_BIG = -$0003; // Too many random requested in single call. + MBEDTLS_ERR_HMAC_DRBG_INPUT_TOO_BIG = -$0005; // Input too large (Entropy + additional). + MBEDTLS_ERR_HMAC_DRBG_FILE_IO_ERROR = -$0007; // Read/write error in file. + MBEDTLS_ERR_HMAC_DRBG_ENTROPY_SOURCE_FAILED = -$0009; // The entropy source failed. + + MBEDTLS_ERR_CCM_BAD_INPUT = -$000D; // Bad input parameters to the function. + MBEDTLS_ERR_CCM_AUTH_FAILED = -$000F; // Authenticated decryption failed. + MBEDTLS_ERR_CCM_HW_ACCEL_FAILED = -$0011; // CCM hardware accelerator failed. + + MBEDTLS_ERR_GCM_AUTH_FAILED = -$0012; // Authenticated decryption failed. + MBEDTLS_ERR_GCM_HW_ACCEL_FAILED = -$0013; // GCM hardware accelerator failed. + MBEDTLS_ERR_GCM_BAD_INPUT = -$0014; // Bad input parameters to function. + + MBEDTLS_ERR_BLOWFISH_INVALID_KEY_LENGTH = -$0016; // Invalid key length. + MBEDTLS_ERR_BLOWFISH_HW_ACCEL_FAILED = -$0017; // Blowfish hardware accelerator failed. + MBEDTLS_ERR_BLOWFISH_INVALID_INPUT_LENGTH = -$0018; // Invalid data input length. + + MBEDTLS_ERR_ARC4_HW_ACCEL_FAILED = -$0019; // ARC4 hardware accelerator failed. + + MBEDTLS_ERR_THREADING_FEATURE_UNAVAILABLE = -$001A; // The selected feature is not available. + MBEDTLS_ERR_THREADING_BAD_INPUT_DATA = -$001C; // Bad input parameters to function. + MBEDTLS_ERR_THREADING_MUTEX_ERROR = -$001E; // Locking / unlocking / free failed with error code. + + MBEDTLS_ERR_AES_INVALID_KEY_LENGTH = -$0020; // Invalid key length. + MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH = -$0022; // Invalid data input length. + + MBEDTLS_ERR_AES_FEATURE_UNAVAILABLE = -$0023; // Feature not available. For example, an unsupported AES key size. + MBEDTLS_ERR_AES_HW_ACCEL_FAILED = -$0025; // AES hardware accelerator failed. + + MBEDTLS_ERR_CAMELLIA_INVALID_KEY_LENGTH = -$0024; // Invalid key length. + MBEDTLS_ERR_CAMELLIA_INVALID_INPUT_LENGTH = -$0026; // Invalid data input length. + MBEDTLS_ERR_CAMELLIA_HW_ACCEL_FAILED = -$0027; // Camellia hardware accelerator failed. + + MBEDTLS_ERR_XTEA_INVALID_INPUT_LENGTH = -$0028; // The data input has an invalid length. + MBEDTLS_ERR_XTEA_HW_ACCEL_FAILED = -$0029; // XTEA hardware accelerator failed. + + MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL = -$002A; // Output buffer too small. + MBEDTLS_ERR_BASE64_INVALID_CHARACTER = -$002C; // Invalid character in input. + + MBEDTLS_ERR_MD2_HW_ACCEL_FAILED = -$002B; // MD2 hardware accelerator failed + MBEDTLS_ERR_MD4_HW_ACCEL_FAILED = -$002D; // MD4 hardware accelerator failed + MBEDTLS_ERR_MD5_HW_ACCEL_FAILED = -$002F; // MD5 hardware accelerator failed + + MBEDTLS_ERR_OID_NOT_FOUND = -$002E; // OID is not found. + MBEDTLS_ERR_OID_BUF_TOO_SMALL = -$000B; // output buffer is too small + + MBEDTLS_ERR_PADLOCK_DATA_MISALIGNED = -$0030; // Input data should be aligned. + + MBEDTLS_ERR_RIPEMD160_HW_ACCEL_FAILED = -$0031; // RIPEMD160 hardware accelerator failed + + MBEDTLS_ERR_DES_INVALID_INPUT_LENGTH = -$0032; // The data input has an invalid length. + MBEDTLS_ERR_DES_HW_ACCEL_FAILED = -$0033; // DES hardware accelerator failed. + + MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED = -$0034; // The entropy source failed. + MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG = -$0036; // The requested random buffer length is too big. + MBEDTLS_ERR_CTR_DRBG_INPUT_TOO_BIG = -$0038; // The input (entropy + additional data) is too large. + MBEDTLS_ERR_CTR_DRBG_FILE_IO_ERROR = -$003A; // Read or write error in file. + + MBEDTLS_ERR_SHA1_HW_ACCEL_FAILED = -$0035; // SHA-1 hardware accelerator failed + MBEDTLS_ERR_SHA256_HW_ACCEL_FAILED = -$0037; // SHA-256 hardware accelerator failed + MBEDTLS_ERR_SHA512_HW_ACCEL_FAILED = -$0039; // SHA-512 hardware accelerator failed + + MBEDTLS_ERR_ENTROPY_SOURCE_FAILED = -$003C; // Critical entropy source failure. + MBEDTLS_ERR_ENTROPY_MAX_SOURCES = -$003E; // No more sources can be added. + MBEDTLS_ERR_ENTROPY_NO_SOURCES_DEFINED = -$0040; // No sources have been added to poll. + MBEDTLS_ERR_ENTROPY_NO_STRONG_SOURCE = -$003D; // No strong sources have been added to poll. + MBEDTLS_ERR_ENTROPY_FILE_IO_ERROR = -$003F; // Read/write error in file. + + MBEDTLS_ERR_NET_SOCKET_FAILED = -$0042; // Failed to open a socket. + MBEDTLS_ERR_NET_CONNECT_FAILED = -$0044; // The connection to the given server / port failed. + MBEDTLS_ERR_NET_BIND_FAILED = -$0046; // Binding of the socket failed. + MBEDTLS_ERR_NET_LISTEN_FAILED = -$0048; // Could not listen on the socket. + MBEDTLS_ERR_NET_ACCEPT_FAILED = -$004A; // Could not accept the incoming connection. + MBEDTLS_ERR_NET_RECV_FAILED = -$004C; // Reading information from the socket failed. + MBEDTLS_ERR_NET_SEND_FAILED = -$004E; // Sending information through the socket failed. + MBEDTLS_ERR_NET_CONN_RESET = -$0050; // Connection was reset by peer. + MBEDTLS_ERR_NET_UNKNOWN_HOST = -$0052; // Failed to get an IP address for the given hostname. + MBEDTLS_ERR_NET_BUFFER_TOO_SMALL = -$0043; // Buffer is too small to hold the data. + MBEDTLS_ERR_NET_INVALID_CONTEXT = -$0045; // The context is invalid, eg because it was free()ed. + + MBEDTLS_ERR_ASN1_OUT_OF_DATA = -$0060; // Out of data when parsing an ASN1 data structure. + MBEDTLS_ERR_ASN1_UNEXPECTED_TAG = -$0062; // ASN1 tag was of an unexpected value. + MBEDTLS_ERR_ASN1_INVALID_LENGTH = -$0064; // Error when trying to determine the length or invalid length. + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH = -$0066; // Actual length differs from expected length. + MBEDTLS_ERR_ASN1_INVALID_DATA = -$0068; // Data is invalid. (not used) + MBEDTLS_ERR_ASN1_ALLOC_FAILED = -$006A; // Memory allocation failed + MBEDTLS_ERR_ASN1_BUF_TOO_SMALL = -$006C; // Buffer too small when writing ASN.1 data structure. + + MBEDTLS_ERR_CMAC_HW_ACCEL_FAILED = -$007A; // CMAC hardware accelerator failed. + + MBEDTLS_ERR_PEM_NO_HEADER_FOOTER_PRESENT = -$1080; // No PEM header or footer found. + MBEDTLS_ERR_PEM_INVALID_DATA = -$1100; // PEM string is not as expected. + MBEDTLS_ERR_PEM_ALLOC_FAILED = -$1180; // Failed to allocate memory. + MBEDTLS_ERR_PEM_INVALID_ENC_IV = -$1200; // RSA IV is not in hex-format. + MBEDTLS_ERR_PEM_UNKNOWN_ENC_ALG = -$1280; // Unsupported key encryption algorithm. + MBEDTLS_ERR_PEM_PASSWORD_REQUIRED = -$1300; // Private key password can't be empty. + MBEDTLS_ERR_PEM_PASSWORD_MISMATCH = -$1380; // Given private key password does not allow for correct decryption. + MBEDTLS_ERR_PEM_FEATURE_UNAVAILABLE = -$1400; // Unavailable feature, e.g. hashing/encryption combination. + MBEDTLS_ERR_PEM_BAD_INPUT_DATA = -$1480; // Bad input parameters to function. + + MBEDTLS_ERR_PKCS12_BAD_INPUT_DATA = -$1F80; // Bad input parameters to function. + MBEDTLS_ERR_PKCS12_FEATURE_UNAVAILABLE = -$1F00; // Feature not available, e.g. unsupported encryption scheme. + MBEDTLS_ERR_PKCS12_PBE_INVALID_FORMAT = -$1E80; // PBE ASN.1 data not as expected. + MBEDTLS_ERR_PKCS12_PASSWORD_MISMATCH = -$1E00; // Given private key password does not allow for correct decryption. + + MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE = -$2080; // Unavailable feature, e.g. RSA hashing/encryption combination. + MBEDTLS_ERR_X509_UNKNOWN_OID = -$2100; // Requested OID is unknown. + MBEDTLS_ERR_X509_INVALID_FORMAT = -$2180; // The CRT/CRL/CSR format is invalid, e.g. different type expected. + MBEDTLS_ERR_X509_INVALID_VERSION = -$2200; // The CRT/CRL/CSR version element is invalid. + MBEDTLS_ERR_X509_INVALID_SERIAL = -$2280; // The serial tag or value is invalid. + MBEDTLS_ERR_X509_INVALID_ALG = -$2300; // The algorithm tag or value is invalid. + MBEDTLS_ERR_X509_INVALID_NAME = -$2380; // The name tag or value is invalid. + MBEDTLS_ERR_X509_INVALID_DATE = -$2400; // The date tag or value is invalid. + MBEDTLS_ERR_X509_INVALID_SIGNATURE = -$2480; // The signature tag or value invalid. + MBEDTLS_ERR_X509_INVALID_EXTENSIONS = -$2500; // The extension tag or value is invalid. + MBEDTLS_ERR_X509_UNKNOWN_VERSION = -$2580; // CRT/CRL/CSR has an unsupported version number. + MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG = -$2600; // Signature algorithm (oid) is unsupported. + MBEDTLS_ERR_X509_SIG_MISMATCH = -$2680; // Signature algorithms do not match. (see \c ::mbedtls_x509_crt sig_oid) + MBEDTLS_ERR_X509_CERT_VERIFY_FAILED = -$2700; // Certificate verification failed, e.g. CRL, CA or signature check failed. + MBEDTLS_ERR_X509_CERT_UNKNOWN_FORMAT = -$2780; // Format not recognized as DER or PEM. + MBEDTLS_ERR_X509_BAD_INPUT_DATA = -$2800; // Input invalid. + MBEDTLS_ERR_X509_ALLOC_FAILED = -$2880; // Allocation of memory failed. + MBEDTLS_ERR_X509_FILE_IO_ERROR = -$2900; // Read/write of file failed. + MBEDTLS_ERR_X509_BUFFER_TOO_SMALL = -$2980; // Destination buffer is too small. + MBEDTLS_ERR_X509_FATAL_ERROR = -$3000; // A fatal error occured, eg the chain is too long or the vrfy callback failed. + + MBEDTLS_ERR_PKCS5_BAD_INPUT_DATA = -$2F80; // Bad input parameters to function. + MBEDTLS_ERR_PKCS5_INVALID_FORMAT = -$2F00; // Unexpected ASN.1 data. + MBEDTLS_ERR_PKCS5_FEATURE_UNAVAILABLE = -$2E80; // Requested encryption or digest alg not available. + MBEDTLS_ERR_PKCS5_PASSWORD_MISMATCH = -$2E00; // Given private key password does not allow for correct decryption. + + MBEDTLS_ERR_DHM_BAD_INPUT_DATA = -$3080; // Bad input parameters. + MBEDTLS_ERR_DHM_READ_PARAMS_FAILED = -$3100; // Reading of the DHM parameters failed. + MBEDTLS_ERR_DHM_MAKE_PARAMS_FAILED = -$3180; // Making of the DHM parameters failed. + MBEDTLS_ERR_DHM_READ_PUBLIC_FAILED = -$3200; // Reading of the public values failed. + MBEDTLS_ERR_DHM_MAKE_PUBLIC_FAILED = -$3280; // Making of the public value failed. + MBEDTLS_ERR_DHM_CALC_SECRET_FAILED = -$3300; // Calculation of the DHM secret failed. + MBEDTLS_ERR_DHM_INVALID_FORMAT = -$3380; // The ASN.1 data is not formatted correctly. + MBEDTLS_ERR_DHM_ALLOC_FAILED = -$3400; // Allocation of memory failed. + MBEDTLS_ERR_DHM_FILE_IO_ERROR = -$3480; // Read or write of file failed. + MBEDTLS_ERR_DHM_HW_ACCEL_FAILED = -$3500; // DHM hardware accelerator failed. + MBEDTLS_ERR_DHM_SET_GROUP_FAILED = -$3580; // Setting the modulus and generator failed. + + MBEDTLS_ERR_PK_ALLOC_FAILED = -$3F80; // Memory allocation failed. + MBEDTLS_ERR_PK_TYPE_MISMATCH = -$3F00; // Type mismatch, eg attempt to encrypt with an ECDSA key + MBEDTLS_ERR_PK_BAD_INPUT_DATA = -$3E80; // Bad input parameters to function. + MBEDTLS_ERR_PK_FILE_IO_ERROR = -$3E00; // Read/write of file failed. + MBEDTLS_ERR_PK_KEY_INVALID_VERSION = -$3D80; // Unsupported key version + MBEDTLS_ERR_PK_KEY_INVALID_FORMAT = -$3D00; // Invalid key tag or value. + MBEDTLS_ERR_PK_UNKNOWN_PK_ALG = -$3C80; // Key algorithm is unsupported (only RSA and EC are supported). + MBEDTLS_ERR_PK_PASSWORD_REQUIRED = -$3C00; // Private key password can't be empty. + MBEDTLS_ERR_PK_PASSWORD_MISMATCH = -$3B80; // Given private key password does not allow for correct decryption. + MBEDTLS_ERR_PK_INVALID_PUBKEY = -$3B00; // The pubkey tag or value is invalid (only RSA and EC are supported). + MBEDTLS_ERR_PK_INVALID_ALG = -$3A80; // The algorithm tag or value is invalid. + MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE = -$3A00; // Elliptic curve is unsupported (only NIST curves are supported). + MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE = -$3980; // Unavailable feature, e.g. RSA disabled for RSA key. + MBEDTLS_ERR_PK_SIG_LEN_MISMATCH = -$3900; // The signature is valid but its length is less than expected. + MBEDTLS_ERR_PK_HW_ACCEL_FAILED = -$3880; // PK hardware accelerator failed. + + MBEDTLS_ERR_RSA_BAD_INPUT_DATA = -$4080; // Bad input parameters to function. + MBEDTLS_ERR_RSA_INVALID_PADDING = -$4100; // Input data contains invalid padding and is rejected. + MBEDTLS_ERR_RSA_KEY_GEN_FAILED = -$4180; // Something failed during generation of a key. + MBEDTLS_ERR_RSA_KEY_CHECK_FAILED = -$4200; // Key failed to pass the validity check of the library. + MBEDTLS_ERR_RSA_PUBLIC_FAILED = -$4280; // The public key operation failed. + MBEDTLS_ERR_RSA_PRIVATE_FAILED = -$4300; // The private key operation failed. + MBEDTLS_ERR_RSA_VERIFY_FAILED = -$4380; // The PKCS#1 verification failed. + MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE = -$4400; // The output buffer for decryption is not large enough. + MBEDTLS_ERR_RSA_RNG_FAILED = -$4480; // The random generator failed to generate non-zeros. + MBEDTLS_ERR_RSA_UNSUPPORTED_OPERATION = -$4500; // The implementation does not offer the requested operation, for example, because of security violations or lack of functionality. + MBEDTLS_ERR_RSA_HW_ACCEL_FAILED = -$4580; // RSA hardware accelerator failed. + + MBEDTLS_ERR_ECP_BAD_INPUT_DATA = -$4F80; // Bad input parameters to function. + MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL = -$4F00; // The buffer is too small to write to. + MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE = -$4E80; // Requested curve not available. + MBEDTLS_ERR_ECP_VERIFY_FAILED = -$4E00; // The signature is not valid. + MBEDTLS_ERR_ECP_ALLOC_FAILED = -$4D80; // Memory allocation failed. + MBEDTLS_ERR_ECP_RANDOM_FAILED = -$4D00; // Generation of random value, such as (ephemeral) key, failed. + MBEDTLS_ERR_ECP_INVALID_KEY = -$4C80; // Invalid private or public key. + MBEDTLS_ERR_ECP_SIG_LEN_MISMATCH = -$4C00; // Signature is valid but shorter than the user-supplied length. + MBEDTLS_ERR_ECP_HW_ACCEL_FAILED = -$4B80; // ECP hardware accelerator failed. + + MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE = -$5080; // The selected feature is not available. + MBEDTLS_ERR_MD_BAD_INPUT_DATA = -$5100; // Bad input parameters to function. + MBEDTLS_ERR_MD_ALLOC_FAILED = -$5180; // Failed to allocate memory. + MBEDTLS_ERR_MD_FILE_IO_ERROR = -$5200; // Opening or reading of file failed. + MBEDTLS_ERR_MD_HW_ACCEL_FAILED = -$5280; // MD hardware accelerator failed. + + MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE = -$6080; // The selected feature is not available. + MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA = -$6100; // Bad input parameters. + MBEDTLS_ERR_CIPHER_ALLOC_FAILED = -$6180; // Failed to allocate memory. + MBEDTLS_ERR_CIPHER_INVALID_PADDING = -$6200; // Input data contains invalid padding and is rejected. + MBEDTLS_ERR_CIPHER_FULL_BLOCK_EXPECTED = -$6280; // Decryption of block requires a full block. + MBEDTLS_ERR_CIPHER_AUTH_FAILED = -$6300; // Authentication failed (for AEAD modes). + MBEDTLS_ERR_CIPHER_INVALID_CONTEXT = -$6380; // The context is invalid. For example, because it was freed. + MBEDTLS_ERR_CIPHER_HW_ACCEL_FAILED = -$6400; // Cipher hardware accelerator failed. + + MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE = -$7080; // The requested feature is not available. + MBEDTLS_ERR_SSL_BAD_INPUT_DATA = -$7100; // Bad input parameters to function. + MBEDTLS_ERR_SSL_INVALID_MAC = -$7180; // Verification of the message MAC failed. + MBEDTLS_ERR_SSL_INVALID_RECORD = -$7200; // An invalid SSL record was received. + MBEDTLS_ERR_SSL_CONN_EOF = -$7280; // The connection indicated an EOF. + MBEDTLS_ERR_SSL_UNKNOWN_CIPHER = -$7300; // An unknown cipher was received. + MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN = -$7380; // The server has no ciphersuites in common with the client. + MBEDTLS_ERR_SSL_NO_RNG = -$7400; // No RNG was provided to the SSL module. + MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE = -$7480; // No client certification received from the client, but required by the authentication mode. + MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE = -$7500; // Our own certificate(s) is/are too large to send in an SSL message. + MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED = -$7580; // The own certificate is not set, but needed by the server. + MBEDTLS_ERR_SSL_PRIVATE_KEY_REQUIRED = -$7600; // The own private key or pre-shared key is not set, but needed. + MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED = -$7680; // No CA Chain is set, but required to operate. + MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE = -$7700; // An unexpected message was received from our peer. + MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE = -$7780; // A fatal alert message was received from our peer. + MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED = -$7800; // Verification of our peer failed. + MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY = -$7880; // The peer notified us that the connection is going to be closed. + MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO = -$7900; // Processing of the ClientHello handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO = -$7980; // Processing of the ServerHello handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE = -$7A00; // Processing of the Certificate handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_REQUEST = -$7A80; // Processing of the CertificateRequest handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_SERVER_KEY_EXCHANGE = -$7B00; // Processing of the ServerKeyExchange handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_SERVER_HELLO_DONE = -$7B80; // Processing of the ServerHelloDone handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE = -$7C00; // Processing of the ClientKeyExchange handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_RP = -$7C80; // Processing of the ClientKeyExchange handshake message failed in DHM / ECDH Read Public. + MBEDTLS_ERR_SSL_BAD_HS_CLIENT_KEY_EXCHANGE_CS = -$7D00; // Processing of the ClientKeyExchange handshake message failed in DHM / ECDH Calculate Secret. + MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE_VERIFY = -$7D80; // Processing of the CertificateVerify handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_CHANGE_CIPHER_SPEC = -$7E00; // Processing of the ChangeCipherSpec handshake message failed. + MBEDTLS_ERR_SSL_BAD_HS_FINISHED = -$7E80; // Processing of the Finished handshake message failed. + MBEDTLS_ERR_SSL_ALLOC_FAILED = -$7F00; // Memory allocation failed + MBEDTLS_ERR_SSL_HW_ACCEL_FAILED = -$7F80; // Hardware acceleration function returned with error + MBEDTLS_ERR_SSL_HW_ACCEL_FALLTHROUGH = -$6F80; // Hardware acceleration function skipped / left alone data + MBEDTLS_ERR_SSL_COMPRESSION_FAILED = -$6F00; // Processing of the compression / decompression failed + MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION = -$6E80; // Handshake protocol not within min/max boundaries + MBEDTLS_ERR_SSL_BAD_HS_NEW_SESSION_TICKET = -$6E00; // Processing of the NewSessionTicket handshake message failed. + MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED = -$6D80; // Session ticket has expired. + MBEDTLS_ERR_SSL_PK_TYPE_MISMATCH = -$6D00; // Public key type mismatch (eg, asked for RSA key exchange and presented EC key) + MBEDTLS_ERR_SSL_UNKNOWN_IDENTITY = -$6C80; // Unknown identity received (eg, PSK identity) + MBEDTLS_ERR_SSL_INTERNAL_ERROR = -$6C00; // Internal error (eg, unexpected failure in lower-level module) + MBEDTLS_ERR_SSL_COUNTER_WRAPPING = -$6B80; // A counter would wrap (eg, too many messages exchanged). + MBEDTLS_ERR_SSL_WAITING_SERVER_HELLO_RENEGO = -$6B00; // Unexpected message at ServerHello in renegotiation. + MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED = -$6A80; // DTLS client must retry for hello verification + MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL = -$6A00; // A buffer is too small to receive or write a message + MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE = -$6980; // None of the common ciphersuites is usable (eg, no suitable certificate, see debug messages). + MBEDTLS_ERR_SSL_WANT_READ = -$6900; // Connection requires a read call. + MBEDTLS_ERR_SSL_WANT_WRITE = -$6880; // Connection requires a write call. + MBEDTLS_ERR_SSL_TIMEOUT = -$6800; // The operation timed out. + MBEDTLS_ERR_SSL_CLIENT_RECONNECT = -$6780; // The client initiated a reconnect from the same port. + MBEDTLS_ERR_SSL_UNEXPECTED_RECORD = -$6700; // Record header looks valid but is not expected. + MBEDTLS_ERR_SSL_NON_FATAL = -$6680; // The alert message received indicates a non-fatal error. + MBEDTLS_ERR_SSL_INVALID_VERIFY_HASH = -$6600; // Couldn't set the hash for verifying CertificateVerify + + // Various constants + + MBEDTLS_SSL_MAJOR_VERSION_3 = 3; + MBEDTLS_SSL_MINOR_VERSION_0 = 0; // SSL v3.0 + MBEDTLS_SSL_MINOR_VERSION_1 = 1; // TLS v1.0 + MBEDTLS_SSL_MINOR_VERSION_2 = 2; // TLS v1.1 + MBEDTLS_SSL_MINOR_VERSION_3 = 3; // TLS v1.2 + + MBEDTLS_SSL_TRANSPORT_STREAM = 0; // TLS + MBEDTLS_SSL_TRANSPORT_DATAGRAM = 1; // DTLS + + MBEDTLS_SSL_MAX_HOST_NAME_LEN = 255; // Maximum host name defined in RFC 1035 + + // RFC 6066 section 4, see also mfl_code_to_length in ssl_tls.c + // NONE must be zero so that memset()ing structure to zero works + + MBEDTLS_SSL_MAX_FRAG_LEN_NONE = 0; // don't use this extension + MBEDTLS_SSL_MAX_FRAG_LEN_512 = 1; // MaxFragmentLength 2^9 + MBEDTLS_SSL_MAX_FRAG_LEN_1024 = 2; // MaxFragmentLength 2^10 + MBEDTLS_SSL_MAX_FRAG_LEN_2048 = 3; // MaxFragmentLength 2^11 + MBEDTLS_SSL_MAX_FRAG_LEN_4096 = 4; // MaxFragmentLength 2^12 + MBEDTLS_SSL_MAX_FRAG_LEN_INVALID = 5; // first invalid value + + MBEDTLS_SSL_IS_CLIENT = 0; + MBEDTLS_SSL_IS_SERVER = 1; + + MBEDTLS_SSL_IS_NOT_FALLBACK = 0; + MBEDTLS_SSL_IS_FALLBACK = 1; + + MBEDTLS_SSL_EXTENDED_MS_DISABLED = 0; + MBEDTLS_SSL_EXTENDED_MS_ENABLED = 1; + + MBEDTLS_SSL_ETM_DISABLED = 0; + MBEDTLS_SSL_ETM_ENABLED = 1; + + MBEDTLS_SSL_COMPRESS_NULL = 0; + MBEDTLS_SSL_COMPRESS_DEFLATE = 1; + + MBEDTLS_SSL_VERIFY_NONE = 0; + MBEDTLS_SSL_VERIFY_OPTIONAL = 1; + MBEDTLS_SSL_VERIFY_REQUIRED = 2; + MBEDTLS_SSL_VERIFY_UNSET = 3; // Used only for sni_authmode + + MBEDTLS_SSL_LEGACY_RENEGOTIATION = 0; + MBEDTLS_SSL_SECURE_RENEGOTIATION = 1; + + MBEDTLS_SSL_RENEGOTIATION_DISABLED = 0; + MBEDTLS_SSL_RENEGOTIATION_ENABLED = 1; + + MBEDTLS_SSL_ANTI_REPLAY_DISABLED = 0; + MBEDTLS_SSL_ANTI_REPLAY_ENABLED = 1; + + MBEDTLS_SSL_RENEGOTIATION_NOT_ENFORCED = -1; + MBEDTLS_SSL_RENEGO_MAX_RECORDS_DEFAULT = 16; + + MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION = 0; + MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION = 1; + MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE = 2; + + MBEDTLS_SSL_TRUNC_HMAC_DISABLED = 0; + MBEDTLS_SSL_TRUNC_HMAC_ENABLED = 1; + MBEDTLS_SSL_TRUNCATED_HMAC_LEN = 10; // 80 bits, rfc 6066 section 7 + + MBEDTLS_SSL_SESSION_TICKETS_DISABLED = 0; + MBEDTLS_SSL_SESSION_TICKETS_ENABLED = 1; + + MBEDTLS_SSL_CBC_RECORD_SPLITTING_DISABLED = 0; + MBEDTLS_SSL_CBC_RECORD_SPLITTING_ENABLED = 1; + + MBEDTLS_SSL_ARC4_ENABLED = 0; + MBEDTLS_SSL_ARC4_DISABLED = 1; + + MBEDTLS_SSL_PRESET_DEFAULT = 0; + MBEDTLS_SSL_PRESET_SUITEB = 2; + + MBEDTLS_SSL_CERT_REQ_CA_LIST_ENABLED = 1; + MBEDTLS_SSL_CERT_REQ_CA_LIST_DISABLED = 0; + + // Default range for DTLS retransmission timer value, in milliseconds. + // RFC 6347 4.2.4.1 says from 1 second to 60 seconds. + + MBEDTLS_SSL_DTLS_TIMEOUT_DFL_MIN = 1000; + MBEDTLS_SSL_DTLS_TIMEOUT_DFL_MAX = 60000; + + MBEDTLS_SSL_INITIAL_HANDSHAKE = 0; + MBEDTLS_SSL_RENEGOTIATION_IN_PROGRESS = 1; // In progress + MBEDTLS_SSL_RENEGOTIATION_DONE = 2; // Done or aborted + MBEDTLS_SSL_RENEGOTIATION_PENDING = 3; // Requested (server only) + + MBEDTLS_SSL_RETRANS_PREPARING = 0; + MBEDTLS_SSL_RETRANS_SENDING = 1; + MBEDTLS_SSL_RETRANS_WAITING = 2; + MBEDTLS_SSL_RETRANS_FINISHED = 3; + + MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC = 20; + MBEDTLS_SSL_MSG_ALERT = 21; + MBEDTLS_SSL_MSG_HANDSHAKE = 22; + MBEDTLS_SSL_MSG_APPLICATION_DATA = 23; + + MBEDTLS_SSL_ALERT_LEVEL_WARNING = 1; + MBEDTLS_SSL_ALERT_LEVEL_FATAL = 2; + + MBEDTLS_SSL_ALERT_MSG_CLOSE_NOTIFY = 0; // 0x00 + MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE = 10; // 0x0A + MBEDTLS_SSL_ALERT_MSG_BAD_RECORD_MAC = 20; // 0x14 + MBEDTLS_SSL_ALERT_MSG_DECRYPTION_FAILED = 21; // 0x15 + MBEDTLS_SSL_ALERT_MSG_RECORD_OVERFLOW = 22; // 0x16 + MBEDTLS_SSL_ALERT_MSG_DECOMPRESSION_FAILURE = 30; // 0x1E + MBEDTLS_SSL_ALERT_MSG_HANDSHAKE_FAILURE = 40; // 0x28 + MBEDTLS_SSL_ALERT_MSG_NO_CERT = 41; // 0x29 + MBEDTLS_SSL_ALERT_MSG_BAD_CERT = 42; // 0x2A + MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT = 43; // 0x2B + MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED = 44; // 0x2C + MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED = 45; // 0x2D + MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN = 46; // 0x2E + MBEDTLS_SSL_ALERT_MSG_ILLEGAL_PARAMETER = 47; // 0x2F + MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA = 48; // 0x30 + MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED = 49; // 0x31 + MBEDTLS_SSL_ALERT_MSG_DECODE_ERROR = 50; // 0x32 + MBEDTLS_SSL_ALERT_MSG_DECRYPT_ERROR = 51; // 0x33 + MBEDTLS_SSL_ALERT_MSG_EXPORT_RESTRICTION = 60; // 0x3C + MBEDTLS_SSL_ALERT_MSG_PROTOCOL_VERSION = 70; // 0x46 + MBEDTLS_SSL_ALERT_MSG_INSUFFICIENT_SECURITY = 71; // 0x47 + MBEDTLS_SSL_ALERT_MSG_INTERNAL_ERROR = 80; // 0x50 + MBEDTLS_SSL_ALERT_MSG_INAPROPRIATE_FALLBACK = 86; // 0x56 + MBEDTLS_SSL_ALERT_MSG_USER_CANCELED = 90; // 0x5A + MBEDTLS_SSL_ALERT_MSG_NO_RENEGOTIATION = 100; // 0x64 + MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_EXT = 110; // 0x6E + MBEDTLS_SSL_ALERT_MSG_UNRECOGNIZED_NAME = 112; // 0x70 + MBEDTLS_SSL_ALERT_MSG_UNKNOWN_PSK_IDENTITY = 115; // 0x73 + MBEDTLS_SSL_ALERT_MSG_NO_APPLICATION_PROTOCOL = 120; // 0x78 + + MBEDTLS_SSL_HS_HELLO_REQUEST = 0; + MBEDTLS_SSL_HS_CLIENT_HELLO = 1; + MBEDTLS_SSL_HS_SERVER_HELLO = 2; + MBEDTLS_SSL_HS_HELLO_VERIFY_REQUEST = 3; + MBEDTLS_SSL_HS_NEW_SESSION_TICKET = 4; + MBEDTLS_SSL_HS_CERTIFICATE = 11; + MBEDTLS_SSL_HS_SERVER_KEY_EXCHANGE = 12; + MBEDTLS_SSL_HS_CERTIFICATE_REQUEST = 13; + MBEDTLS_SSL_HS_SERVER_HELLO_DONE = 14; + MBEDTLS_SSL_HS_CERTIFICATE_VERIFY = 15; + MBEDTLS_SSL_HS_CLIENT_KEY_EXCHANGE = 16; + MBEDTLS_SSL_HS_FINISHED = 20; + + // TLS extensions + MBEDTLS_TLS_EXT_SERVERNAME = 0; + MBEDTLS_TLS_EXT_SERVERNAME_HOSTNAME = 0; + MBEDTLS_TLS_EXT_MAX_FRAGMENT_LENGTH = 1; + MBEDTLS_TLS_EXT_TRUNCATED_HMAC = 4; + MBEDTLS_TLS_EXT_SUPPORTED_ELLIPTIC_CURVES = 10; + MBEDTLS_TLS_EXT_SUPPORTED_POINT_FORMATS = 11; + MBEDTLS_TLS_EXT_SIG_ALG = 13; + MBEDTLS_TLS_EXT_ALPN = 16; + MBEDTLS_TLS_EXT_ENCRYPT_THEN_MAC = 22; // 0x16 + MBEDTLS_TLS_EXT_EXTENDED_MASTER_SECRET = $0017; // 23 + MBEDTLS_TLS_EXT_SESSION_TICKET = 35; + MBEDTLS_TLS_EXT_ECJPAKE_KKPP = 256; // experimental + MBEDTLS_TLS_EXT_RENEGOTIATION_INFO = $FF01; + + MBEDTLS_ENTROPY_SOURCE_STRONG = 1; // Entropy source is strong + MBEDTLS_ENTROPY_SOURCE_WEAK = 0; // Entropy source is weak + +{$REGION 'libmbedcrypto'} +function mbedtls_entropy_add_source(ctx: PMbedtls_Entropy_Context; f_source: TEntropyFunc; p_source: Pointer; threshold: Size_T; strong: Integer): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_entropy_add_source'; +procedure mbedtls_entropy_free(ctx: PMbedtls_Entropy_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_entropy_free'; +function mbedtls_entropy_func(data: Pointer; output: MarshaledAString; len: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_entropy_func'; +procedure mbedtls_entropy_init(ctx: PMbedtls_Entropy_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_entropy_init'; + +procedure mbedtls_ctr_drbg_init(ctx: PMbedtls_CTR_DRBG_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_ctr_drbg_init'; +function mbedtls_ctr_drbg_seed(ctx: PMbedtls_CTR_DRBG_Context; f_entropy: TEntropyFunc; p_entropy: Pointer; custom: MarshaledAString; len: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_ctr_drbg_seed'; +function mbedtls_ctr_drbg_random_with_add(p_rng: Pointer; output: MarshaledAString; output_len: Size_T; additional: MarshaledAString; add_len: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_ctr_drbg_random_with_add'; +function mbedtls_ctr_drbg_random(p_rng: Pointer; output: MarshaledAString; output_len: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_ctr_drbg_random'; +procedure mbedtls_ctr_drbg_free(ctx: PMbedtls_CTR_DRBG_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_ctr_drbg_free'; + +function mbedtls_pk_parse_key(pk: PMbedtls_PK_Context; key: PByte; keylen: Size_T; pwd: PByte; pwdlen: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_pk_parse_key'; + +procedure mbedtls_pk_init(ctx: PMbedtls_PK_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_pk_init'; +procedure mbedtls_pk_free(ctx: PMbedtls_PK_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_pk_free'; + +function mbedtls_version_get_number: Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_version_get_number'; +procedure mbedtls_version_get_string(string_: MarshaledAString); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_version_get_string'; +procedure mbedtls_version_get_string_full(string_: MarshaledAString); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_version_get_string_full'; + +procedure mbedtls_strerror(errnum: Integer; buffer: MarshaledAString; buflen: Size_T); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_strerror'; + +procedure mbedtls_threading_set_alt(_mutex_init: Pointer; _mutex_free: Pointer; _mutex_lock: Pointer; _mutex_unlock: Pointer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_threading_set_alt'; +procedure mbedtls_threading_free_alt; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_CRYPTO{$ENDIF} name _PU + 'mbedtls_threading_free_alt'; +{$ENDREGION} + +{$REGION 'libmbedtls'} +function mbedtls_ssl_close_notify(ssl: PMbedtls_SSL_Context): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_close_notify'; +procedure mbedtls_ssl_free(ssl: PMbedtls_SSL_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_free'; +function mbedtls_ssl_get_verify_result(ssl: PMbedtls_SSL_Context): UInt32_T; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_verify_result'; +function mbedtls_ssl_get_ciphersuite(const ssl: PMbedtls_SSL_Context): MarshaledAString; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_ciphersuite'; +function mbedtls_ssl_get_version(const ssl: PMbedtls_SSL_Context): MarshaledAString; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_version'; +function mbedtls_ssl_get_max_frag_len(const ssl: PMbedtls_SSL_Context): Size_T; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_max_frag_len'; +function mbedtls_ssl_get_peer_cert(const ssl: PMbedtls_SSL_Context): PMbedtls_X509_CRT; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_peer_cert'; +function mbedtls_ssl_get_session(const ssl: PMbedtls_SSL_Context; session: PMbedtls_SSL_Session): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_session'; +function mbedtls_ssl_get_ciphersuite_name(const ciphersuite_id: Integer): MarshaledAString; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_ciphersuite_name'; +function mbedtls_ssl_get_ciphersuite_id(const ciphersuite_name: MarshaledAString): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_ciphersuite_id'; +function mbedtls_ssl_handshake(ssl: PMbedtls_SSL_Context): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_handshake'; +procedure mbedtls_ssl_init(ssl: PMbedtls_SSL_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_init'; +function mbedtls_ssl_list_ciphersuites: PInteger; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_list_ciphersuites'; +function mbedtls_ssl_read(ssl: PMbedtls_SSL_Context; buf: Pointer; len: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_read'; +function mbedtls_ssl_set_hostname(ssl: PMbedtls_SSL_Context; hostname: MarshaledAString): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_set_hostname'; +procedure mbedtls_ssl_set_bio(ssl: PMbedtls_SSL_Context; p_bio: Pointer; f_send: TNetSendFunc; f_recv: TNetRecvFunc; f_recv_timeout: TNetRecvTimeoutFunc); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_set_bio'; +procedure mbedtls_ssl_set_timer_cb(ssl: PMbedtls_SSL_Context; p_timer: Pointer; f_set_timer: TSetTimerFunc; f_get_timer: TGetTimerFunc); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_set_timer_cb'; +function mbedtls_ssl_setup(ssl: PMbedtls_SSL_Context; conf: PMbedtls_SSL_Config): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_setup'; +function mbedtls_ssl_write(ssl: PMbedtls_SSL_Context; const buf: Pointer; len: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_write'; + +procedure mbedtls_ssl_cache_init(cache: PMbedtls_SSL_Cache_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_cache_init'; + +procedure mbedtls_ssl_config_init(conf: PMbedtls_SSL_Config); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_config_init'; +function mbedtls_ssl_config_defaults(conf: PMbedtls_SSL_Config; endpoint: Integer; transport: Integer; preset: Integer): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_config_defaults'; +procedure mbedtls_ssl_conf_authmode(conf: PMbedtls_SSL_Config; authmode: Integer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_authmode'; +procedure mbedtls_ssl_conf_ca_chain(conf: PMbedtls_SSL_Config; ca_chain: PMbedtls_X509_CRT; ca_crl: PMbedtls_X509_Crl); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_ca_chain'; +procedure mbedtls_ssl_conf_transport(conf: PMbedtls_SSL_Config; transport: Integer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_transport'; +procedure mbedtls_ssl_conf_rng(conf: PMbedtls_SSL_Config; f_rng: TrngFunc; p_rng: Pointer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_rng'; +procedure mbedtls_ssl_conf_dbg(conf: PMbedtls_SSL_Config; f_dbg: TdbgFunc; p_dbg: Pointer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_dbg'; +procedure mbedtls_ssl_config_free(conf: PMbedtls_SSL_Config); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_config_free'; + +procedure mbedtls_ssl_conf_ciphersuites(conf: PMbedtls_SSL_Config; ciphersuites: PInteger); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_ciphersuites'; + +function mbedtls_ssl_session_reset(ssl: PMbedtls_SSL_Context): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_session_reset'; +function mbedtls_ssl_set_session(ssl: PMbedtls_SSL_Context; const session: PMbedtls_SSL_Session): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_set_session'; +procedure mbedtls_ssl_conf_max_version(conf: PMbedtls_SSL_Config; major, minor: Integer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_max_version'; +procedure mbedtls_ssl_conf_min_version(conf: PMbedtls_SSL_Config; major, minor: Integer); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_min_version'; +function mbedtls_ssl_get_bytes_avail(ssl: PMbedtls_SSL_Context): Size_T; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_get_bytes_avail'; + +procedure mbedtls_ssl_conf_session_cache(conf: PMbedtls_SSL_Config; p_cache: Pointer; f_get_cache: TGetCacheFunc; f_set_cache: TSetCacheFunc); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_session_cache'; + +function mbedtls_ssl_cache_get(data: Pointer; session: PMbedtls_SSL_Session): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_cache_get'; +function mbedtls_ssl_cache_set(data: Pointer; const session: PMbedtls_SSL_Session): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_cache_set'; + +function mbedtls_ssl_conf_own_cert(conf: PMbedtls_SSL_Config; own_cert: PMbedtls_X509_CRT; pk_key: PMbedtls_PK_Context): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_conf_own_cert'; + +procedure mbedtls_ssl_cache_free(cache: PMbedtls_SSL_Cache_Context); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_cache_free'; +{$ENDREGION} + +{$REGION 'libmbedx509'} +procedure mbedtls_x509_crt_init(crt: PMbedtls_X509_CRT); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_X509{$ENDIF} name _PU + 'mbedtls_x509_crt_init'; +procedure mbedtls_x509_crt_free(crt: PMbedtls_X509_CRT); cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_X509{$ENDIF} name _PU + 'mbedtls_x509_crt_free'; +function mbedtls_x509_crt_parse(chain: PMbedtls_X509_CRT; buf: PByte; buflen: Size_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_X509{$ENDIF} name _PU + 'mbedtls_x509_crt_parse'; +function mbedtls_x509_crt_verify_info(buf: MarshaledAString; size_: Size_T; const prefix: MarshaledAString; flags: UInt32_T): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_X509{$ENDIF} name _PU + 'mbedtls_x509_crt_verify_info'; +{$ENDREGION} + +type + TMbedtls_Cipher_ID = ( + MBEDTLS_CIPHER_ID_NONE = 0, // **< Placeholder to mark the end of cipher ID lists. + MBEDTLS_CIPHER_ID_NULL, // **< The identity cipher, treated as a stream cipher. + MBEDTLS_CIPHER_ID_AES, // **< The AES cipher. + MBEDTLS_CIPHER_ID_DES, // **< The DES cipher. + MBEDTLS_CIPHER_ID_3DES, // **< The Triple DES cipher. + MBEDTLS_CIPHER_ID_CAMELLIA, // **< The Camellia cipher. + MBEDTLS_CIPHER_ID_BLOWFISH, // **< The Blowfish cipher. + MBEDTLS_CIPHER_ID_ARC4, // **< The RC4 cipher. + MBEDTLS_CIPHER_ID_ARIA, // **< The Aria cipher. + MBEDTLS_CIPHER_ID_CHACHA20 // **< The ChaCha20 cipher. + ); + + TMbedtls_Cipher_Mode = ( + MBEDTLS_MODE_NONE = 0, // **< None. + MBEDTLS_MODE_ECB, // **< The ECB cipher mode. + MBEDTLS_MODE_CBC, // **< The CBC cipher mode. + MBEDTLS_MODE_CFB, // **< The CFB cipher mode. + MBEDTLS_MODE_OFB, // **< The OFB cipher mode. + MBEDTLS_MODE_CTR, // **< The CTR cipher mode. + MBEDTLS_MODE_GCM, // **< The GCM cipher mode. + MBEDTLS_MODE_STREAM, // **< The stream cipher mode. + MBEDTLS_MODE_CCM, // **< The CCM cipher mode. + MBEDTLS_MODE_XTS, // **< The XTS cipher mode. + MBEDTLS_MODE_CHACHAPOLY // **< The ChaCha-Poly cipher mode. + ); + +function mbedtls_ssl_handshake_client_step(ssl: PMbedtls_SSL_Context): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_handshake_client_step'; + +function mbedtls_ssl_handshake_server_step(ssl: PMbedtls_SSL_Context): Integer; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_ssl_handshake_server_step'; + +function mbedtls_cipher_info_from_values(const cipher_id: TMbedtls_Cipher_ID; key_bitlen: Integer; const mode: TMbedtls_Cipher_Mode): PMbedtls_Cipher_Info; cdecl; external {$IFDEF __HAS_MBED_TLS_LIB__}LIB_MBED_TLS{$ENDIF} name _PU + 'mbedtls_cipher_info_from_values'; +{$ENDREGION} + +function MbedErrToStr(AErrCode: Integer): string; + +implementation + +{$IFDEF __HAS_MBED_TLS_OBJ__} + {$L aesni.obj} + {$L aria.obj} + {$L cmac.obj} + {$L ctr_drbg.obj} + {$L entropy.obj} + {$L entropy_poll.obj} + {$L error.obj} + {$L havege.obj} + {$L hkdf.obj} + {$L md2.obj} + {$L md4.obj} + {$L memory_buffer_alloc.obj} + {$L nist_kw.obj} + {$L pkcs11.obj} + {$L ssl_cache.obj} + {$L ssl_cookie.obj} + {$L ssl_ticket.obj} + {$L threading.obj} + {$L timing.obj} + {$L version.obj} + {$L version_features.obj} + {$L x509write_crt.obj} + {$L x509write_csr.obj} + {$L x509_create.obj} + {$L x509_crl.obj} + {$L x509_csr.obj} + {$L xtea.obj} + {$L asn1write.obj} + {$L pem.obj} + {$L platform.obj} + {$L pkwrite.obj} + {$L pkparse.obj} + {$L base64.obj} + {$L pkcs12.obj} + {$L pkcs5.obj} + {$L arc4.obj} + {$L ssl_cli.obj} + {$L ssl_srv.obj} + {$L ssl_tls.obj} + {$L ssl_ciphersuites.obj} + {$L debug.obj} + {$L x509_crt.obj} + {$L pk.obj} + {$L pk_wrap.obj} + {$L x509.obj} + {$L platform_util.obj} + {$L rsa.obj} + {$L rsa_internal.obj} + {$L oid.obj} + {$L ecjpake.obj} + {$L ecdsa.obj} + {$L ecdh.obj} + {$L ecp.obj} + {$L hmac_drbg.obj} + {$L dhm.obj} + {$L ecp_curves.obj} + {$L asn1parse.obj} + {$L md.obj} + {$L md_wrap.obj} + {$L cipher.obj} + {$L cipher_wrap.obj} + {$L ccm.obj} + {$L gcm.obj} + {$L aes.obj} + {$L camellia.obj} + {$L des.obj} + {$L blowfish.obj} + {$L chachapoly.obj} + {$L chacha20.obj} + {$L md5.obj} + {$L ripemd160.obj} + {$L sha1.obj} + {$L sha256.obj} + {$L sha512.obj} + {$L poly1305.obj} + {$L bignum.obj} +{$ENDIF} + +{$IFDEF __HAS_MBED_TLS_O__} + {$L aesni.o} + {$L aria.o} + {$L cmac.o} + {$L ctr_drbg.o} + {$L entropy.o} + {$L entropy_poll.o} + {$L error.o} + {$L havege.o} + {$L hkdf.o} + {$L md2.o} + {$L md4.o} + {$L memory_buffer_alloc.o} + {$L nist_kw.o} + {$L pkcs11.o} + {$L ssl_cache.o} + {$L ssl_cookie.o} + {$L ssl_ticket.o} + {$L threading.o} + {$L timing.o} + {$L version.o} + {$L version_features.o} + {$L x509write_crt.o} + {$L x509write_csr.o} + {$L x509_create.o} + {$L x509_crl.o} + {$L x509_csr.o} + {$L xtea.o} + {$L asn1write.o} + {$L pem.o} + {$L platform.o} + {$L pkwrite.o} + {$L pkparse.o} + {$L base64.o} + {$L pkcs12.o} + {$L pkcs5.o} + {$L arc4.o} + {$L ssl_cli.o} + {$L ssl_srv.o} + {$L ssl_tls.o} + {$L ssl_ciphersuites.o} + {$L debug.o} + {$L x509_crt.o} + {$L pk.o} + {$L pk_wrap.o} + {$L x509.o} + {$L platform_util.o} + {$L rsa.o} + {$L rsa_internal.o} + {$L oid.o} + {$L ecjpake.o} + {$L ecdsa.o} + {$L ecdh.o} + {$L ecp.o} + {$L hmac_drbg.o} + {$L dhm.o} + {$L ecp_curves.o} + {$L asn1parse.o} + {$L md.o} + {$L md_wrap.o} + {$L cipher.o} + {$L cipher_wrap.o} + {$L ccm.o} + {$L gcm.o} + {$L aes.o} + {$L camellia.o} + {$L des.o} + {$L blowfish.o} + {$L chachapoly.o} + {$L chacha20.o} + {$L md5.o} + {$L ripemd160.o} + {$L sha1.o} + {$L sha256.o} + {$L sha512.o} + {$L poly1305.o} + {$L bignum.o} +{$ENDIF} + +{$IFDEF MSWINDOWS} +type + HCRYPTPROV = ULONG_PTR; + +function CryptAcquireContextA(var phProv: HCRYPTPROV; pszContainer, pszProvider: LPCSTR; dwProvType, dwFlags: DWORD): BOOL; stdcall; external advapi32; + +function CryptGenRandom(hProv: HCRYPTPROV; dwLen: DWORD; pbBuffer: LPBYTE): BOOL; stdcall; external advapi32; + +function CryptReleaseContext(hProv: HCRYPTPROV; dwFlags: ULONG_PTR): BOOL; stdcall; external advapi32; +{$ENDIF} + +procedure _mutex_init(mutex: PTlsMutex); cdecl; +begin + mutex.Lock := TObject.Create; +end; + +procedure _mutex_free(mutex: PTlsMutex); cdecl; +begin + mutex.Lock.DisposeOf; +end; + +function _mutex_lock(mutex: PTlsMutex): Integer; cdecl; +begin + Result := 0; + System.TMonitor.Enter(mutex.Lock); +end; + +function _mutex_unlock(mutex: PTlsMutex): Integer; cdecl; +begin + Result := 0; + System.TMonitor.Exit(mutex.Lock); +end; + +function MbedErrToStr(AErrCode: Integer): string; +var + LBuf: array [0 .. 511] of Byte; +begin + mbedtls_strerror(AErrCode, @LBuf, 512); + Result := TMarshal.ReadStringAsAnsi(TPtrWrapper.Create(@LBuf)); +end; + +{$IFDEF WIN32} +function _calloc(nitems: Size_T; size: Size_T): Pointer; cdecl; +begin + Result := AllocMem(nitems * size); +end; + +procedure __llmul; cdecl; +asm + jmp System.@_llmul +end; + +procedure __lludiv; cdecl; +asm + jmp System.@_lludiv +end; + +procedure __llumod; cdecl; +asm + jmp System.@_llumod +end; + +procedure __lldiv; cdecl; +asm + jmp System.@_lldiv +end; + +procedure __llshl; cdecl; +asm + jmp System.@_llshl +end; +{$ENDIF} + +{$IFDEF WIN64} +function calloc(nitems: Size_T; size: Size_T): Pointer; cdecl; +begin + Result := AllocMem(nitems * size); +end; + +procedure __chkstk; +label + _loop1, _exit; +asm + lea r10, [rsp] + mov r11, r10 + sub r11, rax + and r11w, 0f000h + and r10w, 0f000h + _loop1: + sub r10, 01000h + cmp r10, r11 // more to go? + jl _exit + mov qword [r10], 0 // probe this page + jmp _loop1 + _exit: + ret +end; +{$ENDIF} + +initialization + mbedtls_threading_set_alt(@_mutex_init, @_mutex_free, @_mutex_lock, @_mutex_unlock); + +finalization + mbedtls_threading_free_alt; + +end. diff --git a/ThirdParty/DCS/Net/Net.OpenSSL.pas b/ThirdParty/DCS/Net/Net.OpenSSL.pas new file mode 100644 index 00000000..87abc488 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.OpenSSL.pas @@ -0,0 +1,1377 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.OpenSSL; + +{ + OpenSSL 下载: + https://indy.fulgan.com/SSL/ + https://github.com/leenjewel/openssl_for_ios_and_android + + OpenSSL iOS静态库下载: + https://indy.fulgan.com/SSL/OpenSSLStaticLibs.7z + + LibreSSL 下载: + http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/ + + Linux下需要安装libssl开发包 + sudo apt-get install libssl-dev +} + +// 使用 LibreSSL +// LibreSSL 是 OpenSSL 的一个分支, 由 OpenBSD 维护, 接口与 OpenSSL 兼容 +// 目前(2.4.2) 执行效率比 OpenSSL(1.0.2h) 低 +{.$DEFINE __LIBRE_SSL__} + +// iOS真机必须用openssl的静态库 +{$IF defined(IOS) and defined(CPUARM)} + {$DEFINE __SSL_STATIC__} +{$ENDIF} + +interface + +uses + {$IFDEF MSWINDOWS} + Winapi.Windows, + {$ENDIF} + {$IFDEF POSIX} + Posix.Base, Posix.Pthread, + {$ENDIF} + System.SysUtils, System.SyncObjs; + +const + SSLEAY_DLL = + {$IFDEF MSWINDOWS} + {$IFDEF __LIBRE_SSL__} + 'libssl-39.dll' + {$ELSE} + 'ssleay32.dll' + {$ENDIF} + {$ENDIF} + {$IFDEF POSIX} + {$IFDEF __SSL_STATIC__} + 'libssl.a' + {$ELSEIF defined(MACOS)} + 'libssl.dylib' + {$ELSE} + 'libssl.so' + {$ENDIF} + {$ENDIF}; + + LIBEAY_DLL = + {$IFDEF MSWINDOWS} + {$IFDEF __LIBRE_SSL__} + 'libcrypto-38.dll' + {$ELSE} + 'libeay32.dll' + {$ENDIF} + {$ENDIF} + {$IFDEF POSIX} + {$IFDEF __SSL_STATIC__} + 'libcrypto.a' + {$ELSEIF defined(MACOS)} + 'libcrypto.dylib' + {$ELSE} + 'libcrypto.so' + {$ENDIF} + {$ENDIF}; + + {$IFDEF MSWINDOWS} + _PU = ''; + {$ENDIF} + + SSL_ERROR_NONE = 0; + SSL_ERROR_SSL = 1; + SSL_ERROR_WANT_READ = 2; + SSL_ERROR_WANT_WRITE = 3; + SSL_ERROR_WANT_X509_LOOKUP = 4; + SSL_ERROR_SYSCALL = 5; + SSL_ERROR_ZERO_RETURN = 6; + SSL_ERROR_WANT_CONNECT = 7; + SSL_ERROR_WANT_ACCEPT = 8; + + SSL_ST_CONNECT = $1000; + SSL_ST_ACCEPT = $2000; + SSL_ST_MASK = $0FFF; + SSL_ST_INIT = (SSL_ST_CONNECT or SSL_ST_ACCEPT); + SSL_ST_BEFORE = $4000; + SSL_ST_OK = $03; + SSL_ST_RENEGOTIATE = ($04 or SSL_ST_INIT); + + BIO_CTRL_EOF = 2; + BIO_CTRL_INFO = 3; + BIO_CTRL_PENDING = 10; + SSL_VERIFY_NONE = 0; + SSL_VERIFY_PEER = 1; + SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 2; + SSL_VERIFY_CLIENT_ONCE = 4; + + SSL_CTRL_NEED_TMP_RSA = 1; + SSL_CTRL_SET_TMP_RSA = 2; + SSL_CTRL_SET_TMP_DH = 3; + SSL_CTRL_SET_TMP_ECDH = 4; + SSL_CTRL_SET_TMP_RSA_CB = 5; + SSL_CTRL_SET_TMP_DH_CB = 6; + SSL_CTRL_SET_TMP_ECDH_CB = 7; + SSL_CTRL_GET_SESSION_REUSED = 8; + SSL_CTRL_GET_CLIENT_CERT_REQUEST = 9; + SSL_CTRL_GET_NUM_RENEGOTIATIONS = 10; + SSL_CTRL_CLEAR_NUM_RENEGOTIATIONS = 11; + SSL_CTRL_GET_TOTAL_RENEGOTIATIONS = 12; + SSL_CTRL_GET_FLAGS = 13; + SSL_CTRL_EXTRA_CHAIN_CERT = 14; + SSL_CTRL_SET_MSG_CALLBACK = 15; + SSL_CTRL_SET_MSG_CALLBACK_ARG = 16; + SSL_CTRL_SET_MTU = 17; + + SSL_CTRL_SESS_NUMBER = 20; + SSL_CTRL_SESS_CONNECT = 21; + SSL_CTRL_SESS_CONNECT_GOOD = 22; + SSL_CTRL_SESS_CONNECT_RENEGOTIATE = 23; + SSL_CTRL_SESS_ACCEPT = 24; + SSL_CTRL_SESS_ACCEPT_GOOD = 25; + SSL_CTRL_SESS_ACCEPT_RENEGOTIATE = 26; + SSL_CTRL_SESS_HIT = 27; + SSL_CTRL_SESS_CB_HIT = 28; + SSL_CTRL_SESS_MISSES = 29; + SSL_CTRL_SESS_TIMEOUTS = 30; + SSL_CTRL_SESS_CACHE_FULL = 31; + SSL_CTRL_OPTIONS = 32; + SSL_CTRL_MODE = 33; + SSL_CTRL_GET_READ_AHEAD = 40; + SSL_CTRL_SET_READ_AHEAD = 41; + SSL_CTRL_SET_SESS_CACHE_SIZE = 42; + SSL_CTRL_GET_SESS_CACHE_SIZE = 43; + SSL_CTRL_SET_SESS_CACHE_MODE = 44; + SSL_CTRL_GET_SESS_CACHE_MODE = 45; + SSL_CTRL_GET_MAX_CERT_LIST = 50; + SSL_CTRL_SET_MAX_CERT_LIST = 51; + SSL_CTRL_SET_MAX_SEND_FRAGMENT = 52; + SSL_CTRL_GET_RI_SUPPORT = 76; + SSL_CTRL_CLEAR_OPTIONS = 77; + SSL_CTRL_CLEAR_MODE = 78; + SSL_CTRL_GET_EXTRA_CHAIN_CERTS = 82; + SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS = 83; + SSL_CTRL_CHAIN = 88; + SSL_CTRL_CHAIN_CERT = 89; + SSL_CTRL_GET_CURVES = 90; + SSL_CTRL_SET_CURVES = 91; + SSL_CTRL_SET_CURVES_LIST = 92; + SSL_CTRL_GET_SHARED_CURVE = 93; + SSL_CTRL_SET_ECDH_AUTO = 94; + SSL_CTRL_SET_SIGALGS = 97; + SSL_CTRL_SET_SIGALGS_LIST = 98; + SSL_CTRL_CERT_FLAGS = 99; + SSL_CTRL_CLEAR_CERT_FLAGS = 100; + SSL_CTRL_SET_CLIENT_SIGALGS = 101; + SSL_CTRL_SET_CLIENT_SIGALGS_LIST = 102; + SSL_CTRL_GET_CLIENT_CERT_TYPES = 103; + SSL_CTRL_SET_CLIENT_CERT_TYPES = 104; + SSL_CTRL_BUILD_CERT_CHAIN = 105; + SSL_CTRL_SET_VERIFY_CERT_STORE = 106; + SSL_CTRL_SET_CHAIN_CERT_STORE = 107; + SSL_CTRL_GET_PEER_SIGNATURE_NID = 108; + SSL_CTRL_GET_SERVER_TMP_KEY = 109; + SSL_CTRL_GET_RAW_CIPHERLIST = 110; + SSL_CTRL_GET_EC_POINT_FORMATS = 111; + SSL_CTRL_GET_CHAIN_CERTS = 115; + SSL_CTRL_SELECT_CURRENT_CERT = 116; + SSL_CTRL_SET_CURRENT_CERT = 117; + SSL_CTRL_SET_DH_AUTO = 118; + SSL_CTRL_CHECK_PROTO_VERSION = 119; + DTLS_CTRL_SET_LINK_MTU = 120; + DTLS_CTRL_GET_LINK_MIN_MTU = 121; + SSL_CTRL_GET_EXTMS_SUPPORT = 122; + SSL_CTRL_SET_MIN_PROTO_VERSION = 123; + SSL_CTRL_SET_MAX_PROTO_VERSION = 124; + SSL_CTRL_SET_SPLIT_SEND_FRAGMENT = 125; + SSL_CTRL_SET_MAX_PIPELINES = 126; + + SSL_MODE_ENABLE_PARTIAL_WRITE = $00000001; + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = $00000002; + SSL_MODE_AUTO_RETRY = $00000004; + SSL_MODE_NO_AUTO_CHAIN = $00000008; + + SSL_OP_MICROSOFT_SESS_ID_BUG = $00000001; + SSL_OP_NETSCAPE_CHALLENGE_BUG = $00000002; + SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = $00000008; + SSL_OP_TLSEXT_PADDING = $00000010; + SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG = $00000000; + SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER = $00000020; + SSL_OP_SAFARI_ECDHE_ECDSA_BUG = $00000040; + SSL_OP_MSIE_SSLV2_RSA_PADDING = $00000000; + SSL_OP_SSLEAY_080_CLIENT_DH_BUG = $00000080; + SSL_OP_TLS_D5_BUG = $00000100; + SSL_OP_TLS_BLOCK_PADDING_BUG = $00000200; + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = $00000800; + SSL_OP_ALL = $00000BFF; + SSL_OP_NO_QUERY_MTU = $00001000; + SSL_OP_COOKIE_EXCHANGE = $00002000; + SSL_OP_NO_TICKET = $00004000; + SSL_OP_CISCO_ANYCONNECT = $00008000; + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = $00010000; + SSL_OP_NO_COMPRESSION = $00020000; + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION = $00040000; + SSL_OP_SINGLE_ECDH_USE = $00080000; + SSL_OP_SINGLE_DH_USE = $00100000; + SSL_OP_EPHEMERAL_RSA = $00200000; + SSL_OP_CIPHER_SERVER_PREFERENCE = $00400000; + SSL_OP_TLS_ROLLBACK_BUG = $00800000; + SSL_OP_NO_SSLv2 = $01000000; + SSL_OP_NO_SSLv3 = $02000000; + SSL_OP_NO_TLSv1 = $04000000; + SSL_OP_NO_TLSv1_2 = $08000000; + SSL_OP_NO_TLSv1_1 = $10000000; + SSL_OP_PKCS1_CHECK_1 = $00000000; + SSL_OP_PKCS1_CHECK_2 = $00000000; + SSL_OP_NETSCAPE_CA_DN_BUG = $20000000; + SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG = $40000000; + SSL_OP_CRYPTOPRO_TLSEXT_BUG = $80000000; + + NID_X9_62_prime192v1 = 409; + NID_X9_62_prime192v2 = 410; + NID_X9_62_prime192v3 = 411; + NID_X9_62_prime239v1 = 412; + NID_X9_62_prime239v2 = 413; + NID_X9_62_prime239v3 = 414; + NID_X9_62_prime256v1 = 415; + + CRYPTO_LOCK = 1; + CRYPTO_UNLOCK = 2; + CRYPTO_READ = 4; + CRYPTO_WRITE = 8; + + BIO_FLAGS_READ = 1; + BIO_FLAGS_WRITE = 2; + BIO_FLAGS_IO_SPECIAL = 4; + BIO_FLAGS_RWS = (BIO_FLAGS_READ or + BIO_FLAGS_WRITE or + BIO_FLAGS_IO_SPECIAL); + BIO_FLAGS_SHOULD_RETRY = 8; + + OSSL_VER_0908 = $00908000; + OSSL_VER_1000 = $10000000; + OSSL_VER_1001 = $10001000; + OSSL_VER_1002 = $10002000; + OSSL_VER_1100 = $10100000; + + TLS1_VERSION = $0301; + TLS1_VERSION_MAJOR = $03; + TLS1_VERSION_MINOR = $01; + + TLS1_1_VERSION = $0302; + TLS1_1_VERSION_MAJOR = $03; + TLS1_1_VERSION_MINOR = $02; + + TLS1_2_VERSION = $0303; + TLS1_2_VERSION_MAJOR = $03; + TLS1_2_VERSION_MINOR = $03; + + TLS_MAX_VERSION = TLS1_2_VERSION; + TLS_ANY_VERSION = $10000; + + DTLS1_VERSION = $FEFF; + DTLS1_2_VERSION = $FEFD; + DTLS_MAX_VERSION = DTLS1_2_VERSION; + DTLS1_VERSION_MAJOR = $FE; + + DTLS1_BAD_VER = $0100; + + // Special value for method supporting multiple versions + DTLS_ANY_VERSION = $1FFFF; + +type + size_t = NativeUInt; + + {$REGION 'SSL'} + TSSL_METHOD_st = packed record + Dummy: array [0..0] of Byte; + end; + PSSL_METHOD = ^TSSL_METHOD_st; + + TSSL_CTX_st = packed record + Dummy: array [0..0] of Byte; + end; + PSSL_CTX = ^TSSL_CTX_st; + + TBIO_st = packed record + Dummy: array [0..0] of Byte; + end; + PBIO = ^TBIO_st; + PPBIO = ^PBIO; + + TSSL_st = packed record + Dummy: array [0..0] of Byte; + end; + PSSL = ^TSSL_st; + + TX509_STORE_CTX_st = packed record + Dummy: array [0..0] of Byte; + end; + PX509_STORE_CTX = ^TX509_STORE_CTX_st; + + TEVP_PKEY_st = packed record + Dummy: array [0..0] of Byte; + end; + PEVP_PKEY = ^TEVP_PKEY_st; + PPEVP_PKEY = ^PEVP_PKEY; + + TX509_st = packed record + Dummy: array [0..0] of Byte; + end; + PX509 = ^TX509_st; + PPX509 = ^PX509; + + TX509_STORE_st = packed record + Dummy : array [0..0] of Byte; + end; + PX509_STORE = ^TX509_STORE_st; + + // 0.9.7g, 0.9.8a, 0.9.8e, 1.0.0d + TASN1_STRING_st = record + length : Integer; + type_ : Integer; + data : MarshaledAString; + //* The value of the following field depends on the type being + //* held. It is mostly being used for BIT_STRING so if the + //* input data has a non-zero 'unused bits' value, it will be + //* handled correctly */ + flags : Longword; + end; + PASN1_STRING = ^TASN1_STRING_st; + TASN1_OCTET_STRING = TASN1_STRING_st; + PASN1_OCTET_STRING = ^TASN1_OCTET_STRING; + TASN1_BIT_STRING = TASN1_STRING_st; + PASN1_BIT_STRING = ^TASN1_BIT_STRING; + + TSetVerify_cb = function(Ok: Integer; StoreCtx: PX509_STORE_CTX): Integer; cdecl; + {$ENDREGION} + + {$REGION 'LIBEAY'} + TCRYPTO_THREADID_st = packed record + Dummy: array [0..0] of Byte; + end; + PCRYPTO_THREADID = ^TCRYPTO_THREADID_st; + + TCRYPTO_dynlock_value_st = record + Mutex: TCriticalSection; + end; + PCRYPTO_dynlock_value = ^TCRYPTO_dynlock_value_st; + CRYPTO_dynlock_value = TCRYPTO_dynlock_value_st; + + TBIO_METHOD_st = packed record + Dummy: array [0..0] of Byte; + end; + PBIO_METHOD = ^TBIO_METHOD_st; + + TX509_NAME_st = packed record + Dummy: array [0..0] of Byte; + end; + PX509_NAME = ^TX509_NAME_st; + + TSTACK_st = packed record + Dummy : array [0..0] of Byte; + end; + PSTACK = ^TSTACK_st; + + TASN1_OBJECT_st = packed record + Dummy : array [0..0] of Byte; + end; + PASN1_OBJECT = ^TASN1_OBJECT_st; + + TEC_KEY_st = packed record + Dummy : array [0..0] of Byte; + end; + PEC_KEY = ^TEC_KEY_st; + + TStatLockLockCallback = procedure(Mode: Integer; N: Integer; const _File: MarshaledAString; Line: Integer); cdecl; + TStatLockIDCallback = function: Longword; cdecl; + TCryptoThreadIDCallback = procedure(ID: PCRYPTO_THREADID) cdecl; + + TDynLockCreateCallback = function(const _file: MarshaledAString; Line: Integer): PCRYPTO_dynlock_value; cdecl; + TDynLockLockCallback = procedure(Mode: Integer; L: PCRYPTO_dynlock_value; _File: MarshaledAString; Line: Integer); cdecl; + TDynLockDestroyCallback = procedure(L: PCRYPTO_dynlock_value; _File: MarshaledAString; Line: Integer); cdecl; + pem_password_cb = function(buf: Pointer; size: Integer; rwflag: Integer; userdata: Pointer): Integer; cdecl; + {$ENDREGION} + +{$IFDEF __SSL_STATIC__} + +{$REGION 'SSL-FUNC'} +function SSL_library_init: Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_library_init'; +procedure SSL_load_error_strings; cdecl; + external SSLEAY_DLL name _PU + 'SSL_load_error_strings'; + +function SSLv23_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'SSLv23_method'; +function SSLv23_client_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'SSLv23_client_method'; +function SSLv23_server_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'SSLv23_server_method'; + +function TLSv1_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'TLSv1_method'; +function TLSv1_client_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'TLSv1_client_method'; +function TLSv1_server_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'TLSv1_server_method'; + +{$IF not(defined(MACOS) and not defined(IOS))} +function TLSv1_2_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'TLSv1_2_method'; +{$ENDIF} +function TLSv1_2_client_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'TLSv1_2_client_method'; +function TLSv1_2_server_method: PSSL_METHOD; cdecl; + external SSLEAY_DLL name _PU + 'TLSv1_2_server_method'; + +function SSL_CTX_new(meth: PSSL_METHOD): PSSL_CTX; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_new'; +procedure SSL_CTX_free(ctx: PSSL_CTX); cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_free'; +function SSL_CTX_ctrl(ctx: PSSL_CTX; Cmd: Integer; LArg: Integer; PArg: MarshaledAString): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_ctrl'; +procedure SSL_CTX_set_verify(ctx: PSSL_CTX; mode: Integer; callback: TSetVerify_cb); cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_set_verify'; +function SSL_CTX_set_cipher_list(ctx: PSSL_CTX; CipherString: MarshaledAString): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_set_cipher_list'; +function SSL_CTX_use_PrivateKey(ctx: PSSL_CTX; pkey: PEVP_PKEY): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_use_PrivateKey'; +function SSL_CTX_use_certificate(ctx: PSSL_CTX; cert: PX509): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_use_certificate'; +function SSL_CTX_check_private_key(ctx: PSSL_CTX): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_check_private_key'; + +function SSL_new(ctx: PSSL_CTX): PSSL; cdecl; + external SSLEAY_DLL name _PU + 'SSL_new'; +procedure SSL_set_bio(s: PSSL; rbio, wbio: PBIO); cdecl; + external SSLEAY_DLL name _PU + 'SSL_set_bio'; +function SSL_get_peer_certificate(s: PSSL): PX509; cdecl; + external SSLEAY_DLL name _PU + 'SSL_get_peer_certificate'; +function SSL_get_error(s: PSSL; ret_code: Integer): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_get_error'; + +function SSL_ctrl(S: PSSL; Cmd: Integer; LArg: Integer; PArg: Pointer): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_ctrl'; + +function SSL_shutdown(s: PSSL): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_shutdown'; +procedure SSL_free(s: PSSL); cdecl; + external SSLEAY_DLL name _PU + 'SSL_free'; + +procedure SSL_set_connect_state(s: PSSL); cdecl; + external SSLEAY_DLL name _PU + 'SSL_set_connect_state'; +procedure SSL_set_accept_state(s: PSSL); cdecl; + external SSLEAY_DLL name _PU + 'SSL_set_accept_state'; +function SSL_accept(S: PSSL): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_accept'; +function SSL_connect(S: PSSL): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_connect'; +function SSL_do_handshake(S: PSSL): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_do_handshake'; +function SSL_read(s: PSSL; buf: Pointer; num: Integer): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_read'; +function SSL_write(s: PSSL; const buf: Pointer; num: Integer): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_write'; +function SSL_state(s: PSSL): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_state'; +function SSL_pending(s: PSSL): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_pending'; + +function SSL_CTX_get_cert_store(const Ctx: PSSL_CTX): PX509_STORE; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_get_cert_store'; +function SSL_CTX_add_client_CA(C: PSSL_CTX; CaCert: PX509): Integer; cdecl; + external SSLEAY_DLL name _PU + 'SSL_CTX_add_client_CA'; +{$ENDREGION} + +{$REGION 'LIBEAY-FUNC'} +function SSLeay: Longword; cdecl; + external LIBEAY_DLL name _PU + 'SSLeay'; + +function CRYPTO_set_id_callback(callback: TStatLockIDCallback): Integer; cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_set_id_callback'; + +{$IF not(defined(MACOS) and not defined(IOS))} +// MACOS 内置的 OpenSSL 库版本较低(0.9.x) +// CRYPTO_THREADID_set_callback 只有 OpenSSL 1.0 以上版本才有 +function CRYPTO_THREADID_set_callback(callback: TCryptoThreadIDCallback): Integer; cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_THREADID_set_callback'; +procedure CRYPTO_THREADID_set_numeric(id : PCRYPTO_THREADID; val: LongWord); cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_THREADID_set_numeric'; +procedure CRYPTO_THREADID_set_pointer(id : PCRYPTO_THREADID; ptr: Pointer); cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_THREADID_set_pointer'; +{$ENDIF} + +function CRYPTO_num_locks: Integer; cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_num_locks'; +procedure CRYPTO_set_locking_callback(callback: TStatLockLockCallback); cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_set_locking_callback'; +procedure CRYPTO_set_dynlock_create_callback(callback: TDynLockCreateCallBack); cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_set_dynlock_create_callback'; +procedure CRYPTO_set_dynlock_lock_callback(callback: TDynLockLockCallBack); cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_set_dynlock_lock_callback'; +procedure CRYPTO_set_dynlock_destroy_callback(callback: TDynLockDestroyCallBack); cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_set_dynlock_destroy_callback'; +procedure CRYPTO_cleanup_all_ex_data; cdecl; + external LIBEAY_DLL name _PU + 'CRYPTO_cleanup_all_ex_data'; + +procedure ERR_remove_state(tid: Cardinal); cdecl; + external LIBEAY_DLL name _PU + 'ERR_remove_state'; + +{$IF not(defined(MACOS) and not defined(IOS))} +procedure ERR_remove_thread_state(tid: PCRYPTO_THREADID); cdecl; + external LIBEAY_DLL name _PU + 'ERR_remove_thread_state'; +{$ENDIF} + +procedure ERR_free_strings; cdecl; + external LIBEAY_DLL name _PU + 'ERR_free_strings'; +procedure ERR_error_string_n(err: Cardinal; buf: MarshaledAString; len: size_t); cdecl; + external LIBEAY_DLL name _PU + 'ERR_error_string_n'; +function ERR_get_error: Cardinal; cdecl; + external LIBEAY_DLL name _PU + 'ERR_get_error'; +procedure ERR_clear_error; cdecl; + external LIBEAY_DLL name _PU + 'ERR_clear_error'; + +procedure EVP_cleanup; cdecl; + external LIBEAY_DLL name _PU + 'EVP_cleanup'; +procedure EVP_PKEY_free(pkey: PEVP_PKEY); cdecl; + external LIBEAY_DLL name _PU + 'EVP_PKEY_free'; + +function BIO_new(BioMethods: PBIO_METHOD): PBIO; cdecl; + external LIBEAY_DLL name _PU + 'BIO_new'; +function BIO_ctrl(bp: PBIO; cmd: Integer; larg: Longint; parg: Pointer): Longint; cdecl; + external LIBEAY_DLL name _PU + 'BIO_ctrl'; +function BIO_new_mem_buf(buf: Pointer; len: Integer): PBIO; cdecl; + external LIBEAY_DLL name _PU + 'BIO_new_mem_buf'; +function BIO_free(b: PBIO): Integer; cdecl; + external LIBEAY_DLL name _PU + 'BIO_free'; +function BIO_s_mem: PBIO_METHOD; cdecl; + external LIBEAY_DLL name _PU + 'BIO_s_mem'; +function BIO_read(b: PBIO; Buf: Pointer; Len: Integer): Integer; cdecl; + external LIBEAY_DLL name _PU + 'BIO_read'; +function BIO_write(b: PBIO; Buf: Pointer; Len: Integer): Integer; cdecl; + external LIBEAY_DLL name _PU + 'BIO_write'; + +function EC_KEY_new_by_curve_name(nid: Integer): PEC_KEY; cdecl; + external LIBEAY_DLL name _PU + 'EC_KEY_new_by_curve_name'; +procedure EC_KEY_free(key: PEC_KEY); cdecl; + external LIBEAY_DLL name _PU + 'EC_KEY_free'; + +function X509_get_issuer_name(cert: PX509): PX509_NAME; cdecl; + external LIBEAY_DLL name _PU + 'X509_get_issuer_name'; +function X509_get_subject_name(cert: PX509): PX509_NAME; cdecl; + external LIBEAY_DLL name _PU + 'X509_get_subject_name'; +procedure X509_free(cert: PX509); cdecl; + external LIBEAY_DLL name _PU + 'X509_free'; +function X509_NAME_print_ex(bout: PBIO; nm: PX509_NAME; indent: Integer; flags: Cardinal): Integer; cdecl; + external LIBEAY_DLL name _PU + 'X509_NAME_print_ex'; +function X509_get_ext_d2i(x: PX509; nid: Integer; var crit, idx: Integer): Pointer; cdecl; + external LIBEAY_DLL name _PU + 'X509_get_ext_d2i'; + +function X509_STORE_add_cert(Store: PX509_STORE; Cert: PX509): Integer; cdecl; + external LIBEAY_DLL name _PU + 'X509_STORE_add_cert'; + +function sk_num(stack: PSTACK): Integer; cdecl; + external LIBEAY_DLL name _PU + 'sk_num'; +function sk_pop(stack: PSTACK): Pointer; cdecl; + external LIBEAY_DLL name _PU + 'sk_pop'; + +function ASN1_BIT_STRING_get_bit(a: PASN1_BIT_STRING; n: Integer): Integer; cdecl; + external LIBEAY_DLL name _PU + 'ASN1_BIT_STRING_get_bit'; +function OBJ_obj2nid(o: PASN1_OBJECT): Integer; cdecl; + external LIBEAY_DLL name _PU + 'OBJ_obj2nid'; +function OBJ_nid2sn(n: Integer): MarshaledAString; cdecl; + external LIBEAY_DLL name _PU + 'OBJ_nid2sn'; +function ASN1_STRING_data(x: PASN1_STRING): Pointer; cdecl; + external LIBEAY_DLL name _PU + 'ASN1_STRING_data'; +function PEM_read_bio_X509(bp: PBIO; x: PPX509; cb: pem_password_cb; u: Pointer): PX509; cdecl; + external LIBEAY_DLL name _PU + 'PEM_read_bio_X509'; +function PEM_read_bio_X509_AUX(bp: PBIO; x: PPX509; cb: pem_password_cb; u: Pointer): PX509; cdecl; + external LIBEAY_DLL name _PU + 'PEM_read_bio_X509_AUX'; +function PEM_read_bio_PrivateKey(bp: PBIO; x: PPEVP_PKEY; cb: pem_password_cb; u: Pointer): PEVP_PKEY; cdecl; + external LIBEAY_DLL name _PU + 'PEM_read_bio_PrivateKey'; + +procedure OPENSSL_add_all_algorithms_noconf; cdecl; + external LIBEAY_DLL name _PU + 'OPENSSL_add_all_algorithms_noconf'; +procedure OPENSSL_add_all_algorithms_conf; cdecl; + external LIBEAY_DLL name _PU + 'OPENSSL_add_all_algorithms_conf'; + +procedure OpenSSL_add_all_ciphers; cdecl; + external LIBEAY_DLL name _PU + 'OpenSSL_add_all_ciphers'; +procedure OpenSSL_add_all_digests; cdecl; + external LIBEAY_DLL name _PU + 'OpenSSL_add_all_digests'; +{$ENDREGION} + +{$ENDIF} + +function SSL_CTX_need_tmp_RSA(ctx: PSSL_CTX): Integer; inline; +function SSL_CTX_set_tmp_rsa(ctx: PSSL_CTX; rsa: MarshaledAString): Integer; inline; +function SSL_CTX_set_tmp_dh(ctx: PSSL_CTX; dh: MarshaledAString): Integer; inline; +function SSL_CTX_set_tmp_ecdh(ctx: PSSL_CTX; ecdh: PEC_KEY): Integer; inline; +function SSL_CTX_add_extra_chain_cert(ctx: PSSL_CTX; cert: PX509): Integer; inline; +function SSL_need_tmp_RSA(ssl: PSSL): Integer; inline; +function SSL_set_tmp_rsa(ssl: PSSL; rsa: MarshaledAString): Integer; inline; +function SSL_set_tmp_dh(ssl: PSSL; dh: MarshaledAString): Integer; inline; +function SSL_set_tmp_ecdh(ssl: PSSL; ecdh: MarshaledAString): Integer; inline; + +function SSL_CTX_set_options(ctx: PSSL_CTX; Op: Integer): Integer; inline; +function SSL_CTX_get_options(ctx: PSSL_CTX): Integer; inline; +function SSL_CTX_set_mode(ctx: PSSL_CTX; op: Integer): Integer; inline; +function SSL_CTX_clear_mode(ctx: PSSL_CTX; op: Integer): Integer; inline; +function SSL_CTX_get_mode(ctx: PSSL_CTX): Integer; inline; + +function SSL_set_options(ssl: PSSL; Op: Integer): Integer; inline; +function SSL_get_options(ssl: PSSL): Integer; inline; +function SSL_clear_options(ssl: PSSL; Op: Integer): Integer; inline; + +function BIO_eof(bp: PBIO): Boolean; inline; +function BIO_pending(bp: PBIO): Integer; inline; +function BIO_get_mem_data(bp: PBIO; parg: Pointer): Integer; inline; +function BIO_get_flags(b: PBIO): Integer; inline; +function BIO_should_retry(b: PBIO): Boolean; inline; + +function SSL_is_init_finished(s: PSSL): Boolean; inline; + +function ssl_is_fatal_error(ssl_error: Integer): Boolean; +function ssl_error_message(ssl_error: Integer): string; + +function sk_ASN1_OBJECT_num(stack: PSTACK): Integer; inline; +function sk_GENERAL_NAME_num(stack: PSTACK): Integer; inline; +function sk_GENERAL_NAME_pop(stack: PSTACK): Pointer; inline; + +type + ESsl = class(Exception); + ESslInvalidLib = class(ESsl); + ESslInvalidProc = class(ESsl); + + {$REGION 'SSLTools'} + TSSLTools = class + private class var + FRef: Integer; + public + class procedure LoadSSL; + class procedure UnloadSSL; + class function SSLVersion: Longword; + class function NewCTX(meth: PSSL_METHOD = nil): PSSL_CTX; + class procedure FreeCTX(var AContext: PSSL_CTX); + + class procedure SetCertificate(AContext: PSSL_CTX; ACertBuf: Pointer; ACertBufSize: Integer); overload; + class procedure SetCertificate(AContext: PSSL_CTX; const ACertStr: string); overload; + class procedure SetCertificateFile(AContext: PSSL_CTX; const ACertFile: string); + + class procedure SetPrivateKey(AContext: PSSL_CTX; APKeyBuf: Pointer; APKeyBufSize: Integer); overload; + class procedure SetPrivateKey(AContext: PSSL_CTX; const APKeyStr: string); overload; + class procedure SetPrivateKeyFile(AContext: PSSL_CTX; const APKeyFile: string); + end; + {$ENDREGION} + +{$IFNDEF __SSL_STATIC__} + +var + _SslLibPath: string; + _SslLibHandle: THandle; + _CryptoLibHandle: THandle; + + {$REGION 'SSL-FUNC'} + SSL_library_init: function: Integer; cdecl; + SSL_load_error_strings: procedure; cdecl; + + SSLv23_method: function: PSSL_METHOD; cdecl; + SSLv23_client_method: function: PSSL_METHOD; cdecl; + SSLv23_server_method: function: PSSL_METHOD; cdecl; + + TLSv1_method: function: PSSL_METHOD; cdecl; + TLSv1_client_method: function: PSSL_METHOD; cdecl; + TLSv1_server_method: function: PSSL_METHOD; cdecl; + + TLSv1_2_method: function: PSSL_METHOD; cdecl; + TLSv1_2_client_method: function: PSSL_METHOD; cdecl; + TLSv1_2_server_method: function : PSSL_METHOD; cdecl; + + SSL_CTX_new: function(meth: PSSL_METHOD): PSSL_CTX; cdecl; + SSL_CTX_free: procedure(ctx: PSSL_CTX); cdecl; + SSL_CTX_ctrl: function(ctx: PSSL_CTX; Cmd: Integer; LArg: Integer; PArg: MarshaledAString): Integer; cdecl; + SSL_CTX_set_verify: procedure(ctx: PSSL_CTX; mode: Integer; callback: TSetVerify_cb); cdecl; + SSL_CTX_set_cipher_list: function(ctx: PSSL_CTX; CipherString: MarshaledAString): Integer; cdecl; + SSL_CTX_use_PrivateKey: function(ctx: PSSL_CTX; pkey: PEVP_PKEY): Integer; cdecl; + SSL_CTX_use_certificate: function(ctx: PSSL_CTX; cert: PX509): Integer; cdecl; + SSL_CTX_check_private_key: function(ctx: PSSL_CTX): Integer; cdecl; + + SSL_new: function(ctx: PSSL_CTX): PSSL; cdecl; + SSL_set_bio: procedure(s: PSSL; rbio, wbio: PBIO); cdecl; + SSL_get_peer_certificate: function(s: PSSL): PX509; cdecl; + SSL_get_error: function(s: PSSL; ret_code: Integer): Integer; cdecl; + + SSL_ctrl: function(S: PSSL; Cmd: Integer; LArg: Integer; PArg: Pointer): Integer; cdecl; + + SSL_shutdown: function(s: PSSL): Integer; cdecl; + SSL_free: procedure(s: PSSL); cdecl; + + SSL_set_connect_state: procedure(s: PSSL); cdecl; + SSL_set_accept_state: procedure(s: PSSL); cdecl; + SSL_accept: function(S: PSSL): Integer; cdecl; + SSL_connect: function(S: PSSL): Integer; cdecl; + SSL_do_handshake: function(S: PSSL): Integer; cdecl; + SSL_read: function(s: PSSL; buf: Pointer; num: Integer): Integer; cdecl; + SSL_write: function(s: PSSL; const buf: Pointer; num: Integer): Integer; cdecl; + SSL_state: function(s: PSSL): Integer; cdecl; + SSL_pending: function(s: PSSL): Integer; cdecl; + + SSL_CTX_get_cert_store: function(const Ctx: PSSL_CTX): PX509_STORE; cdecl; + SSL_CTX_add_client_CA: function(C: PSSL_CTX; CaCert: PX509): Integer; cdecl; + {$ENDREGION} + + {$REGION 'LIBEAY-FUNC'} + SSLeay: function: Longword; cdecl; + + CRYPTO_set_id_callback: function(callback: TStatLockIDCallback): Integer; cdecl; + + {$IF not(defined(MACOS) and not defined(IOS))} + // MACOS 内置的 OpenSSL 库版本较低(0.9.x) + // CRYPTO_THREADID_set_callback 只有 OpenSSL 1.0 以上版本才有 + CRYPTO_THREADID_set_callback: function(callback: TCryptoThreadIDCallback): Integer; cdecl; + CRYPTO_THREADID_set_numeric: procedure(id : PCRYPTO_THREADID; val: LongWord); cdecl; + CRYPTO_THREADID_set_pointer: procedure(id : PCRYPTO_THREADID; ptr: Pointer); cdecl; + {$ENDIF} + + CRYPTO_num_locks: function: Integer; cdecl; + CRYPTO_set_locking_callback: procedure(callback: TStatLockLockCallback); cdecl; + CRYPTO_set_dynlock_create_callback: procedure(callback: TDynLockCreateCallBack); cdecl; + CRYPTO_set_dynlock_lock_callback: procedure(callback: TDynLockLockCallBack); cdecl; + CRYPTO_set_dynlock_destroy_callback: procedure(callback: TDynLockDestroyCallBack); cdecl; + CRYPTO_cleanup_all_ex_data: procedure; cdecl; + + ERR_remove_state: procedure(tid: Cardinal); cdecl; + + {$IF not(defined(MACOS) and not defined(IOS))} + ERR_remove_thread_state: procedure(tid: PCRYPTO_THREADID); cdecl; + {$ENDIF} + + ERR_free_strings: procedure; cdecl; + ERR_error_string_n: procedure(err: Cardinal; buf: MarshaledAString; len: size_t); cdecl; + ERR_get_error: function: Cardinal; cdecl; + ERR_clear_error: procedure; cdecl; + + EVP_cleanup: procedure; cdecl; + EVP_PKEY_free: procedure(pkey: PEVP_PKEY); cdecl; + + BIO_new: function(BioMethods: PBIO_METHOD): PBIO; cdecl; + BIO_ctrl: function(bp: PBIO; cmd: Integer; larg: Longint; parg: Pointer): Longint; cdecl; + BIO_new_mem_buf: function(buf: Pointer; len: Integer): PBIO; cdecl; + BIO_free: function(b: PBIO): Integer; cdecl; + BIO_s_mem: function: PBIO_METHOD; cdecl; + BIO_read: function(b: PBIO; Buf: Pointer; Len: Integer): Integer; cdecl; + BIO_write: function(b: PBIO; Buf: Pointer; Len: Integer): Integer; cdecl; + + EC_KEY_new_by_curve_name: function(nid: Integer): PEC_KEY; cdecl; + EC_KEY_free: procedure(key: PEC_KEY); cdecl; + + X509_get_issuer_name: function(cert: PX509): PX509_NAME; cdecl; + X509_get_subject_name: function(cert: PX509): PX509_NAME; cdecl; + X509_free: procedure(cert: PX509); cdecl; + X509_NAME_print_ex: function(bout: PBIO; nm: PX509_NAME; indent: Integer; flags: Cardinal): Integer; cdecl; + X509_get_ext_d2i: function(x: PX509; nid: Integer; var crit, idx: Integer): Pointer; cdecl; + + X509_STORE_add_cert: function(Store: PX509_STORE; Cert: PX509): Integer; cdecl; + + sk_num: function(stack: PSTACK): Integer; cdecl; + sk_pop: function(stack: PSTACK): Pointer; cdecl; + + ASN1_BIT_STRING_get_bit: function(a: PASN1_BIT_STRING; n: Integer): Integer; cdecl; + OBJ_obj2nid: function(o: PASN1_OBJECT): Integer; cdecl; + OBJ_nid2sn: function(n: Integer): MarshaledAString; cdecl; + ASN1_STRING_data: function(x: PASN1_STRING): Pointer; cdecl; + PEM_read_bio_X509: function(bp: PBIO; x: PPX509; cb: pem_password_cb; u: Pointer): PX509; cdecl; + PEM_read_bio_X509_AUX: function(bp: PBIO; x: PPX509; cb: pem_password_cb; u: Pointer): PX509; cdecl; + PEM_read_bio_PrivateKey: function(bp: PBIO; x: PPEVP_PKEY; cb: pem_password_cb; u: Pointer): PEVP_PKEY; cdecl; + + OPENSSL_add_all_algorithms_noconf: procedure; cdecl; + OPENSSL_add_all_algorithms_conf: procedure; cdecl; + + OpenSSL_add_all_ciphers: procedure; cdecl; + OpenSSL_add_all_digests: procedure; cdecl; + {$ENDREGION} + +{$ENDIF} + +implementation + +uses + System.IOUtils; + +var + _FSslLocks: TArray; + _FSslVersion: Longword; + +function SSL_CTX_need_tmp_RSA(ctx: PSSL_CTX): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_NEED_TMP_RSA, 0, nil); +end; + +function SSL_CTX_set_tmp_rsa(ctx: PSSL_CTX; rsa: MarshaledAString): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TMP_RSA, 0, rsa); +end; + +function SSL_CTX_set_tmp_dh(ctx: PSSL_CTX; dh: MarshaledAString): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TMP_DH, 0, dh); +end; + +function SSL_CTX_set_tmp_ecdh(ctx: PSSL_CTX; ecdh: PEC_KEY): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TMP_ECDH, 0, MarshaledAString(ecdh)); +end; + +function SSL_CTX_add_extra_chain_cert(ctx: PSSL_CTX; cert: PX509): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_EXTRA_CHAIN_CERT, 0, MarshaledAString(cert)); +end; + +function SSL_need_tmp_RSA(ssl: PSSL): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_NEED_TMP_RSA, 0, nil); +end; + +function SSL_set_tmp_rsa(ssl: PSSL; rsa: MarshaledAString): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_SET_TMP_RSA, 0, rsa); +end; + +function SSL_set_tmp_dh(ssl: PSSL; dh: MarshaledAString): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_SET_TMP_DH, 0, dh); +end; + +function SSL_set_tmp_ecdh(ssl: PSSL; ecdh: MarshaledAString): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_SET_TMP_ECDH, 0, ecdh); +end; + +function SSL_CTX_set_options(ctx: PSSL_CTX; Op: Integer): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, Op, nil); +end; + +function SSL_CTX_get_options(ctx: PSSL_CTX): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, 0, nil); +end; + +function SSL_CTX_set_mode(ctx: PSSL_CTX; op: Integer): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_MODE, op, nil); +end; + +function SSL_CTX_clear_mode(ctx: PSSL_CTX; op: Integer): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_CLEAR_MODE, op, nil); +end; + +function SSL_CTX_get_mode(ctx: PSSL_CTX): Integer; +begin + Result := SSL_CTX_ctrl(ctx, SSL_CTRL_MODE, 0, nil); +end; + +function SSL_set_options(ssl: PSSL; Op: Integer): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_OPTIONS, Op, nil); +end; + +function SSL_get_options(ssl: PSSL): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_OPTIONS, 0, nil); +end; + +function SSL_clear_options(ssl: PSSL; Op: Integer): Integer; +begin + Result := SSL_ctrl(ssl, SSL_CTRL_CLEAR_OPTIONS, Op, nil); +end; + +function BIO_eof(bp: PBIO): Boolean; +begin + Result := (BIO_ctrl(bp, BIO_CTRL_EOF, 0, nil) <> 0); +end; + +function BIO_pending(bp: PBIO): Integer; +begin + Result := BIO_ctrl(bp, BIO_CTRL_PENDING, 0, nil); +end; + +function BIO_get_mem_data(bp: PBIO; parg: Pointer): Integer; +begin + Result := BIO_ctrl(bp, BIO_CTRL_INFO, 0, parg); +end; + +function sk_ASN1_OBJECT_num(stack: PSTACK): Integer; +begin + Result := sk_num(stack); +end; + +function sk_GENERAL_NAME_num(stack: PSTACK): Integer; +begin + Result := sk_num(stack); +end; + +function sk_GENERAL_NAME_pop(stack: PSTACK): Pointer; +begin + Result := sk_pop(stack); +end; + +function BIO_get_flags(b: PBIO): Integer; +begin + // This is a hack : BIO structure has not been defined. But I know + // flags member is the 6th field in the structure (index is 5) + // This could change when OpenSSL is updated. Check "struct bio_st". + Result := PInteger(MarshaledAString(b) + 3 * SizeOf(Pointer) + 2 * SizeOf(Integer))^; +end; + +function BIO_should_retry(b: PBIO): Boolean; +begin + Result := ((BIO_get_flags(b) and BIO_FLAGS_SHOULD_RETRY) <> 0); +end; + +function SSL_is_init_finished(s: PSSL): Boolean; +begin + Result := (SSL_state(s) = SSL_ST_OK); +end; + +function ssl_is_fatal_error(ssl_error: Integer): Boolean; +begin + case ssl_error of + SSL_ERROR_NONE, + SSL_ERROR_WANT_READ, + SSL_ERROR_WANT_WRITE, + SSL_ERROR_WANT_CONNECT, + SSL_ERROR_WANT_ACCEPT: Result := False; + else + Result := True; + end; +end; + +function ssl_error_message(ssl_error: Integer): string; +var + LPtr: TPtrWrapper; +begin + LPtr := TMarshal.AllocMem(1024); + try + ERR_error_string_n(ssl_error, LPtr.ToPointer, 1024); + Result := TMarshal.ReadStringAsAnsi(LPtr); + finally + TMarshal.FreeMem(LPtr); + end; +end; + +function set_id_callback: Longword; cdecl; +begin + Result := GetCurrentThreadID; +end; + +{$IF not(defined(MACOS) and not defined(IOS))} +procedure ssl_threadid_callback(ID : PCRYPTO_THREADID); cdecl; +begin + CRYPTO_THREADID_set_numeric(ID, GetCurrentThreadId); +end; +{$ENDIF} + +procedure ssl_lock_callback(Mode, N: Integer; + const _File: MarshaledAString; Line: Integer); cdecl; +begin + if(mode and CRYPTO_LOCK <> 0) then + _FSslLocks[N].Enter + else + _FSslLocks[N].Leave; +end; + +procedure ssl_lock_dyn_callback(Mode: Integer; + L: PCRYPTO_dynlock_value; _File: MarshaledAString; Line: Integer); cdecl; +begin + if (Mode and CRYPTO_LOCK <> 0) then + L.Mutex.Enter + else + L.Mutex.Leave; +end; + +function ssl_lock_dyn_create_callback( + const _file: MarshaledAString; Line: Integer): PCRYPTO_dynlock_value; cdecl; +begin + New(Result); + Result.Mutex := TCriticalSection.Create; +end; + +procedure ssl_lock_dyn_destroy_callback( + L: PCRYPTO_dynlock_value; _File: MarshaledAString; Line: Integer); cdecl; +begin + L.Mutex.Free; + Dispose(L); +end; + +{$IFNDEF __SSL_STATIC__} + +function GetSslLibPath: string; +begin + if (_SslLibPath <> '') then + Result := IncludeTrailingPathDelimiter(_SslLibPath) + else + Result := _SslLibPath; +end; + +function LoadSslLib(const ALibName: string): THandle; +begin + Result := SafeLoadLibrary(GetSslLibPath + ALibName); + if (Result = 0) then + raise ESslInvalidLib.CreateFmt('无效的SSL库: %s', [ALibName]); +end; + +function GetSslLibProc(const ALibHandle: THandle; const AProcName: string): Pointer; +begin + Result := GetProcAddress(ALibHandle, PChar(AProcName)); + if (Result = nil) then + raise ESslInvalidProc.CreateFmt('无效的SSL接口函数: %s', [AProcName]); +end; + +procedure LoadSslLibs; +begin + if (_SslLibHandle = 0) then + begin + _SslLibHandle := LoadSslLib(SSLEAY_DLL); + + @SSL_library_init := GetSslLibProc(_SslLibHandle, 'SSL_library_init'); + @SSL_load_error_strings := GetSslLibProc(_SslLibHandle, 'SSL_load_error_strings'); + + @SSLv23_method := GetSslLibProc(_SslLibHandle, 'SSLv23_method'); + @SSLv23_client_method := GetSslLibProc(_SslLibHandle, 'SSLv23_client_method'); + @SSLv23_server_method := GetSslLibProc(_SslLibHandle, 'SSLv23_server_method'); + + @TLSv1_method := GetSslLibProc(_SslLibHandle, 'TLSv1_method'); + @TLSv1_client_method := GetSslLibProc(_SslLibHandle, 'TLSv1_client_method'); + @TLSv1_server_method := GetSslLibProc(_SslLibHandle, 'TLSv1_server_method'); + + {$IF not(defined(MACOS) and not defined(IOS))} + @TLSv1_2_method := GetSslLibProc(_SslLibHandle, 'TLSv1_2_method'); + @TLSv1_2_client_method := GetSslLibProc(_SslLibHandle, 'TLSv1_2_client_method'); + @TLSv1_2_server_method := GetSslLibProc(_SslLibHandle, 'TLSv1_2_server_method'); + {$ENDIF} + + @SSL_CTX_new := GetSslLibProc(_SslLibHandle, 'SSL_CTX_new'); + @SSL_CTX_free := GetSslLibProc(_SslLibHandle, 'SSL_CTX_free'); + @SSL_CTX_ctrl := GetSslLibProc(_SslLibHandle, 'SSL_CTX_ctrl'); + @SSL_CTX_set_verify := GetSslLibProc(_SslLibHandle, 'SSL_CTX_set_verify'); + @SSL_CTX_set_cipher_list := GetSslLibProc(_SslLibHandle, 'SSL_CTX_set_cipher_list'); + @SSL_CTX_use_PrivateKey := GetSslLibProc(_SslLibHandle, 'SSL_CTX_use_PrivateKey'); + @SSL_CTX_use_certificate := GetSslLibProc(_SslLibHandle, 'SSL_CTX_use_certificate'); + @SSL_CTX_check_private_key := GetSslLibProc(_SslLibHandle, 'SSL_CTX_check_private_key'); + + @SSL_new := GetSslLibProc(_SslLibHandle, 'SSL_new'); + @SSL_set_bio := GetSslLibProc(_SslLibHandle, 'SSL_set_bio'); + @SSL_get_peer_certificate := GetSslLibProc(_SslLibHandle, 'SSL_get_peer_certificate'); + @SSL_get_error := GetSslLibProc(_SslLibHandle, 'SSL_get_error'); + + @SSL_ctrl := GetSslLibProc(_SslLibHandle, 'SSL_ctrl'); + + @SSL_shutdown := GetSslLibProc(_SslLibHandle, 'SSL_shutdown'); + @SSL_free := GetSslLibProc(_SslLibHandle, 'SSL_free'); + + @SSL_set_connect_state := GetSslLibProc(_SslLibHandle, 'SSL_set_connect_state'); + @SSL_set_accept_state := GetSslLibProc(_SslLibHandle, 'SSL_set_accept_state'); + @SSL_accept := GetSslLibProc(_SslLibHandle, 'SSL_accept'); + @SSL_connect := GetSslLibProc(_SslLibHandle, 'SSL_connect'); + @SSL_do_handshake := GetSslLibProc(_SslLibHandle, 'SSL_do_handshake'); + @SSL_read := GetSslLibProc(_SslLibHandle, 'SSL_read'); + @SSL_write := GetSslLibProc(_SslLibHandle, 'SSL_write'); + @SSL_state := GetSslLibProc(_SslLibHandle, 'SSL_state'); + @SSL_pending := GetSslLibProc(_SslLibHandle, 'SSL_pending'); + + @SSL_CTX_get_cert_store := GetSslLibProc(_SslLibHandle, 'SSL_CTX_get_cert_store'); + @SSL_CTX_add_client_CA := GetSslLibProc(_SslLibHandle, 'SSL_CTX_add_client_CA'); + end; + + if (_CryptoLibHandle = 0) then + begin + _CryptoLibHandle := LoadSslLib(LIBEAY_DLL); + + @SSLeay := GetSslLibProc(_CryptoLibHandle, 'SSLeay'); + + @CRYPTO_set_id_callback := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_set_id_callback'); + + {$IF not(defined(MACOS) and not defined(IOS))} + // MACOS 内置的 OpenSSL 库版本较低(0.9.x) + // CRYPTO_THREADID_set_callback 只有 OpenSSL 1.0 以上版本才有 + @CRYPTO_THREADID_set_callback := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_THREADID_set_callback'); + @CRYPTO_THREADID_set_numeric := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_THREADID_set_numeric'); + @CRYPTO_THREADID_set_pointer := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_THREADID_set_pointer'); + {$ENDIF} + + @CRYPTO_num_locks := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_num_locks'); + @CRYPTO_set_locking_callback := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_set_locking_callback'); + @CRYPTO_set_dynlock_create_callback := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_set_dynlock_create_callback'); + @CRYPTO_set_dynlock_lock_callback := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_set_dynlock_lock_callback'); + @CRYPTO_set_dynlock_destroy_callback := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_set_dynlock_destroy_callback'); + @CRYPTO_cleanup_all_ex_data := GetSslLibProc(_CryptoLibHandle, 'CRYPTO_cleanup_all_ex_data'); + + @ERR_remove_state := GetSslLibProc(_CryptoLibHandle, 'ERR_remove_state'); + + {$IF not(defined(MACOS) and not defined(IOS))} + @ERR_remove_thread_state := GetSslLibProc(_CryptoLibHandle, 'ERR_remove_thread_state'); + {$ENDIF} + + @ERR_free_strings := GetSslLibProc(_CryptoLibHandle, 'ERR_free_strings'); + @ERR_error_string_n := GetSslLibProc(_CryptoLibHandle, 'ERR_error_string_n'); + @ERR_get_error := GetSslLibProc(_CryptoLibHandle, 'ERR_get_error'); + @ERR_clear_error := GetSslLibProc(_CryptoLibHandle, 'ERR_clear_error'); + + @EVP_cleanup := GetSslLibProc(_CryptoLibHandle, 'EVP_cleanup'); + @EVP_PKEY_free := GetSslLibProc(_CryptoLibHandle, 'EVP_PKEY_free'); + + @BIO_new := GetSslLibProc(_CryptoLibHandle, 'BIO_new'); + @BIO_ctrl := GetSslLibProc(_CryptoLibHandle, 'BIO_ctrl'); + @BIO_new_mem_buf := GetSslLibProc(_CryptoLibHandle, 'BIO_new_mem_buf'); + @BIO_free := GetSslLibProc(_CryptoLibHandle, 'BIO_free'); + @BIO_s_mem := GetSslLibProc(_CryptoLibHandle, 'BIO_s_mem'); + @BIO_read := GetSslLibProc(_CryptoLibHandle, 'BIO_read'); + @BIO_write := GetSslLibProc(_CryptoLibHandle, 'BIO_write'); + + @EC_KEY_new_by_curve_name := GetSslLibProc(_CryptoLibHandle, 'EC_KEY_new_by_curve_name'); + @EC_KEY_free := GetSslLibProc(_CryptoLibHandle, 'EC_KEY_free'); + + @X509_get_issuer_name := GetSslLibProc(_CryptoLibHandle, 'X509_get_issuer_name'); + @X509_get_subject_name := GetSslLibProc(_CryptoLibHandle, 'X509_get_subject_name'); + @X509_free := GetSslLibProc(_CryptoLibHandle, 'X509_free'); + @X509_NAME_print_ex := GetSslLibProc(_CryptoLibHandle, 'X509_NAME_print_ex'); + @X509_get_ext_d2i := GetSslLibProc(_CryptoLibHandle, 'X509_get_ext_d2i'); + + @X509_STORE_add_cert := GetSslLibProc(_CryptoLibHandle, 'X509_STORE_add_cert'); + + @sk_num := GetSslLibProc(_CryptoLibHandle, 'sk_num'); + @sk_pop := GetSslLibProc(_CryptoLibHandle, 'sk_pop'); + + @ASN1_BIT_STRING_get_bit := GetSslLibProc(_CryptoLibHandle, 'ASN1_BIT_STRING_get_bit'); + @OBJ_obj2nid := GetSslLibProc(_CryptoLibHandle, 'OBJ_obj2nid'); + @OBJ_nid2sn := GetSslLibProc(_CryptoLibHandle, 'OBJ_nid2sn'); + @ASN1_STRING_data := GetSslLibProc(_CryptoLibHandle, 'ASN1_STRING_data'); + @PEM_read_bio_X509 := GetSslLibProc(_CryptoLibHandle, 'PEM_read_bio_X509'); + @PEM_read_bio_X509_AUX := GetSslLibProc(_CryptoLibHandle, 'PEM_read_bio_X509_AUX'); + @PEM_read_bio_PrivateKey := GetSslLibProc(_CryptoLibHandle, 'PEM_read_bio_PrivateKey'); + + @OPENSSL_add_all_algorithms_noconf := GetSslLibProc(_CryptoLibHandle, 'OPENSSL_add_all_algorithms_noconf'); + @OPENSSL_add_all_algorithms_conf := GetSslLibProc(_CryptoLibHandle, 'OPENSSL_add_all_algorithms_conf'); + + @OpenSSL_add_all_ciphers := GetSslLibProc(_CryptoLibHandle, 'OpenSSL_add_all_ciphers'); + @OpenSSL_add_all_digests := GetSslLibProc(_CryptoLibHandle, 'OpenSSL_add_all_digests'); + end; +end; + +procedure UnloadSslLibs; +begin + if (_SslLibHandle <> 0) then + begin + FreeLibrary(_SslLibHandle); + _SslLibHandle := 0; + end; + + if (_CryptoLibHandle <> 0) then + begin + FreeLibrary(_CryptoLibHandle); + _CryptoLibHandle := 0; + end; +end; + +{$ENDIF} + +procedure SslInit; +var + LNumberOfLocks, I: Integer; +begin + SSL_library_init(); + OPENSSL_add_all_algorithms_noconf; + SSL_load_error_strings(); + + _FSslVersion := SSLeay(); + + LNumberOfLocks := CRYPTO_num_locks(); + if(LNumberOfLocks > 0) then + begin + SetLength(_FSslLocks, LNumberOfLocks); + for I := Low(_FSslLocks) to High(_FSslLocks) do + _FSslLocks[I] := TCriticalSection.Create; + end; + + CRYPTO_set_locking_callback(ssl_lock_callback); + {$IF defined(MACOS) and not defined(IOS)} + CRYPTO_set_id_callback(set_id_callback); + {$ELSE} + CRYPTO_THREADID_set_callback(ssl_threadid_callback); + {$ENDIF} +end; + +procedure SslUninit; +var + I: Integer; +begin + CRYPTO_set_locking_callback(nil); + + CRYPTO_cleanup_all_ex_data(); + {$IF defined(MACOS) and not defined(IOS)} + ERR_remove_state(0); + {$ELSE} + ERR_remove_thread_state(nil); + {$ENDIF} + ERR_clear_error(); + ERR_free_strings(); + EVP_cleanup(); + + for I := Low(_FSslLocks) to High(_FSslLocks) do + _FSslLocks[I].Free; + _FSslLocks := nil; +end; + +{ TSSLTools } + +class function TSSLTools.NewCTX(meth: PSSL_METHOD): PSSL_CTX; +begin + if (meth = nil) then + meth := SSLv23_method(); + Result := SSL_CTX_new(meth); +end; + +class function TSSLTools.SSLVersion: Longword; +begin + Result := _FSslVersion; +end; + +class procedure TSSLTools.FreeCTX(var AContext: PSSL_CTX); +begin + SSL_CTX_free(AContext); + AContext := nil; +end; + +class procedure TSSLTools.LoadSSL; +begin + if (TInterlocked.Increment(FRef) <> 1) then Exit; + + {$IFNDEF __SSL_STATIC__} + LoadSslLibs; + {$ENDIF} + + SslInit; +end; + +class procedure TSSLTools.UnloadSSL; +begin + if (TInterlocked.Decrement(FRef) <> 0) then Exit; + + SslUninit; + + {$IFNDEF __SSL_STATIC__} + UnloadSslLibs; + {$ENDIF} +end; + +class procedure TSSLTools.SetCertificate(AContext: PSSL_CTX; ACertBuf: Pointer; + ACertBufSize: Integer); +var + bio_cert: PBIO; + ssl_cert: PX509; + store: PX509_STORE; +begin + bio_cert := BIO_new_mem_buf(ACertBuf, ACertBufSize); + if (bio_cert = nil) then + raise ESsl.Create('分配证书缓存失败'); + + ssl_cert := PEM_read_bio_X509_AUX(bio_cert, nil, nil, nil); + if (ssl_cert = nil) then + raise ESsl.Create('读取证书数据失败'); + + if (SSL_CTX_use_certificate(AContext, ssl_cert) <= 0) then + raise ESsl.Create('使用证书失败'); + + X509_free(ssl_cert); + + store := SSL_CTX_get_cert_store(AContext); + if (store = nil) then + raise ESsl.Create('获取证书仓库失败'); + + // 将证书链中剩余的证书添加到仓库中 + // 有完整证书链在 ssllabs.com 评分中才能评为 A + while not BIO_eof(bio_cert) do + begin + ssl_cert := PEM_read_bio_X509(bio_cert, nil, nil, nil); + if (ssl_cert = nil) then + raise ESsl.Create('读取证书数据失败'); + + if (X509_STORE_add_cert(store, ssl_cert) <= 0) then + raise ESsl.Create('添加证书到仓库失败'); + + X509_free(ssl_cert); + end; + + BIO_free(bio_cert); +end; + +class procedure TSSLTools.SetCertificate(AContext: PSSL_CTX; + const ACertStr: string); +var + LCertBytes: TBytes; +begin + LCertBytes := TEncoding.ANSI.GetBytes(ACertStr); + SetCertificate(AContext, Pointer(LCertBytes), Length(LCertBytes)); +end; + +class procedure TSSLTools.SetCertificateFile(AContext: PSSL_CTX; + const ACertFile: string); +var + LCertBytes: TBytes; +begin + LCertBytes := TFile.ReadAllBytes(ACertFile); + SetCertificate(AContext, Pointer(LCertBytes), Length(LCertBytes)); +end; + +class procedure TSSLTools.SetPrivateKey(AContext: PSSL_CTX; APKeyBuf: Pointer; + APKeyBufSize: Integer); +var + bio_pkey: PBIO; + ssl_pkey: PEVP_PKEY; +begin + bio_pkey := BIO_new_mem_buf(APKeyBuf, APKeyBufSize); + if (bio_pkey = nil) then + raise ESsl.Create('分配私钥缓存失败'); + + ssl_pkey := PEM_read_bio_PrivateKey(bio_pkey, nil, nil, nil); + if (ssl_pkey = nil) then + raise ESsl.Create('读取私钥数据失败'); + + if (SSL_CTX_use_PrivateKey(AContext, ssl_pkey) <= 0) then + raise ESsl.Create('使用私钥失败'); + + EVP_PKEY_free(ssl_pkey); + BIO_free(bio_pkey); + + if (SSL_CTX_check_private_key(AContext) <= 0) then + raise ESsl.Create('私钥与证书的公钥不匹配'); +end; + +class procedure TSSLTools.SetPrivateKey(AContext: PSSL_CTX; + const APKeyStr: string); +var + LPKeyBytes: TBytes; +begin + LPKeyBytes := TEncoding.ANSI.GetBytes(APKeyStr); + SetPrivateKey(AContext, Pointer(LPKeyBytes), Length(LPKeyBytes)); +end; + +class procedure TSSLTools.SetPrivateKeyFile(AContext: PSSL_CTX; + const APKeyFile: string); +var + LPKeyBytes: TBytes; +begin + LPKeyBytes := TFile.ReadAllBytes(APKeyFile); + SetPrivateKey(AContext, Pointer(LPKeyBytes), Length(LPKeyBytes)); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.Posix.inc b/ThirdParty/DCS/Net/Net.Posix.inc new file mode 100644 index 00000000..2542e007 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.Posix.inc @@ -0,0 +1,42 @@ +function PosixSend(ASocket: THandle; ABuf: Pointer; + ALen: Integer): Integer; +var + LBuf: PByte; + LSent, LError: Integer; + LFlags: Integer; +begin + Result := 0; + + // һѾرյ׽ַʱϵͳֱ׳EPIPE쳣³˳ + // LINUX¿sendʱMSG_NOSIGNALܱķ + // OSXпͨ׽ֵSO_NOSIGPIPEﵽͬĿ + {$IF defined(LINUX) or defined(ANDROID)} + LFlags := MSG_NOSIGNAL; + {$ELSE} + LFlags := 0; + {$ENDIF} + + LBuf := ABuf; + while (Result < ALen) do + begin + LSent := TSocketAPI.Send(ASocket, LBuf^, ALen - Result, LFlags); + + if (LSent < 0) then + begin + LError := GetLastError; + + // ϵͳźж, send + if (LError = EINTR) then + Continue + // ͻѱ + else if (LError = EAGAIN) or (LError = EWOULDBLOCK) then + Break + // ͳ + else + Exit(-1); + end; + + Inc(Result, LSent); + Inc(LBuf, LSent); + end; +end; diff --git a/ThirdParty/DCS/Net/Net.RawSocket.pas b/ThirdParty/DCS/Net/Net.RawSocket.pas new file mode 100644 index 00000000..427905e5 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.RawSocket.pas @@ -0,0 +1,189 @@ +unit Net.RawSocket; + +interface + +uses + System.SysUtils, Net.SocketAPI, + {$IFDEF POSIX} + Posix.Base, Posix.SysSocket, Posix.NetinetIn + {$ELSE} + Winapi.Windows, Net.Winsock2, Net.Wship6 + {$ENDIF}; + +type + /// + /// 򵥵׽ֲ + /// + TRawSocket = class + private + FSocket: THandle; + FSockAddr: TRawSockAddrIn; + FPeerAddr: string; + FPeerPort: Word; + public + /// + /// ر Socket + /// + procedure Close; + + /// + /// ӵ, ֧ IPv6 + /// + function Connect(const AHost: string; APort: Word): Integer; + + /// + /// Socket ַָͶ˿, ֧ IPv6 + /// + function Bind(const Addr: string; APort: Word): Integer; + + /// + /// + /// + function Listen(backlog: Integer = SOMAXCONN): Integer; + + /// + /// һ, Socket + /// + function Accept(Addr: PSockAddr; AddrLen: PInteger): THandle; + + /// + /// + /// + function Recv(var Buf; len: Integer; flags: Integer = 0): Integer; + + /// + /// + /// + function Send(const Buf; len: Integer; flags: Integer = 0): Integer; + + /// + /// ݴַָ˿(UDP) + /// + function RecvFrom(const Addr: PSockAddr; var AddrLen: Integer; var Buf; + len: Integer; flags: Integer = 0): Integer; + + /// + /// ݵַָ˿(UDP) + /// + function SendTo(const Addr: PSockAddr; AddrLen: Integer; const Buf; + len: Integer; flags: Integer = 0): Integer; + + /// + /// ж׽ǷЧ + /// + function IsValid: Boolean; + + /// + /// ׽־ + /// + property Socket: THandle read FSocket; + + /// + /// ׽ֵַϢ + /// Connect Զ˵׽, õַԶ˵ַϢ + /// Bind ص׽, õַDZصַϢ + /// + property SockAddr: TRawSockAddrIn read FSockAddr; + property PeerAddr: string read FPeerAddr; + property PeerPort: Word read FPeerPort; + end; + +implementation + +{ TRawSocket } + +procedure TRawSocket.Close; +begin + if (FSocket = INVALID_HANDLE_VALUE) then Exit; + + TSocketAPI.CloseSocket(FSocket); + FSocket := INVALID_HANDLE_VALUE; +end; + +function TRawSocket.Connect(const AHost: string; APort: Word): Integer; +var + LHints: TRawAddrInfo; + LAddrInfo: PRawAddrInfo; +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(AHost, APort.ToString, LHints); + if (LAddrInfo = nil) then Exit(-1); + + try + FSocket := TSocketAPI.NewSocket(LAddrInfo.ai_family, LAddrInfo.ai_socktype, + LAddrInfo.ai_protocol); + if (FSocket = INVALID_HANDLE_VALUE) then Exit(-1); + + FSockAddr.AddrLen := LAddrInfo.ai_addrlen; + Move(LAddrInfo.ai_addr^, FSockAddr.Addr, LAddrInfo.ai_addrlen); + TSocketAPI.ExtractAddrInfo(@FSockAddr.Addr, FSockAddr.AddrLen, FPeerAddr, FPeerPort); + + TSocketAPI.SetKeepAlive(FSocket, 5, 3, 5); + Result := TSocketAPI.Connect(FSocket, @FSockAddr.Addr, FSockAddr.AddrLen); + finally + TSocketAPI.FreeAddrInfo(LAddrInfo); + end; +end; + +function TRawSocket.Bind(const Addr: string; APort: Word): Integer; +var + LHints: TRawAddrInfo; + LAddrInfo: PRawAddrInfo; +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + LHints.ai_family := AF_UNSPEC; + LHints.ai_socktype := SOCK_STREAM; + LHints.ai_protocol := IPPROTO_TCP; + LAddrInfo := TSocketAPI.GetAddrInfo(Addr, APort.ToString, LHints); + if (LAddrInfo = nil) then Exit(-1); + + FSockAddr.AddrLen := LAddrInfo.ai_addrlen; + Move(LAddrInfo.ai_addr^, FSockAddr.Addr, LAddrInfo.ai_addrlen); + TSocketAPI.FreeAddrInfo(LAddrInfo); + + TSocketAPI.ExtractAddrInfo(@FSockAddr.Addr, FSockAddr.AddrLen, FPeerAddr, FPeerPort); + + Result := TSocketAPI.Bind(FSocket, @FSockAddr.Addr, FSockAddr.AddrLen); +end; + +function TRawSocket.Listen(backlog: Integer): Integer; +begin + Result := TSocketAPI.Listen(FSocket, backlog); +end; + +function TRawSocket.Accept(Addr: PSockAddr; AddrLen: PInteger): THandle; +begin + Result := TSocketAPI.Accept(FSocket, Addr, AddrLen); +end; + +function TRawSocket.Recv(var Buf; len, flags: Integer): Integer; +begin + Result := TSocketAPI.Recv(FSocket, Buf, len, flags); +end; + +function TRawSocket.Send(const Buf; len, flags: Integer): Integer; +begin + Result := TSocketAPI.Send(FSocket, Buf, len, flags); +end; + +function TRawSocket.RecvFrom(const Addr: PSockAddr; var AddrLen: Integer; + var Buf; len, flags: Integer): Integer; +begin + Result := TSocketAPI.RecvFrom(FSocket, Addr, AddrLen, Buf, len, flags); +end; + +function TRawSocket.SendTo(const Addr: PSockAddr; AddrLen: Integer; const Buf; + len: Integer; flags: Integer): Integer; +begin + Result := TSocketAPI.SendTo(FSocket, Addr, AddrLen, Buf, len, flags); +end; + +function TRawSocket.IsValid: Boolean; +begin + Result := TSocketAPI.IsValidSocket(FSocket); +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.SocketAPI.pas b/ThirdParty/DCS/Net/Net.SocketAPI.pas new file mode 100644 index 00000000..a88a0877 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.SocketAPI.pas @@ -0,0 +1,776 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Net.SocketAPI; + +interface + +uses + System.SysUtils, + {$IFDEF POSIX} + Posix.Base, Posix.UniStd, Posix.SysSocket, Posix.ArpaInet, Posix.NetinetIn, + Posix.NetDB, Posix.NetinetTCP, Posix.Fcntl, Posix.SysSelect, Posix.StrOpts, + Posix.SysTime, Posix.Errno + {$IFDEF LINUX} + ,Linuxapi.KernelIoctl + {$ENDIF} + {$ELSE} + Winapi.Windows, Net.Winsock2, Net.Wship6 + {$ENDIF}; + +type + TRawSockAddrIn = packed record + AddrLen: Integer; + case Integer of + 0: (Addr: sockaddr_in); + 1: (Addr6: sockaddr_in6); + end; + + {$IFDEF POSIX} + TRawAddrInfo = Posix.NetDB.addrinfo; + {$ELSE} + TRawAddrInfo = Net.Winsock2.ADDRINFOW; + {$ENDIF} + PRawAddrInfo = ^TRawAddrInfo; + + /// + /// ׽ֻӿڷװ + /// + TSocketAPI = class + public + /// + /// ½׽ + /// + class function NewSocket(const ADomain, AType, AProtocol: Integer): THandle; static; + + /// + /// ½ Tcp ׽ + /// + class function NewTcp: THandle; static; + + /// + /// ½ Udp ׽ + /// + class function NewUdp: THandle; static; + + /// + /// ر׽ + /// + class function CloseSocket(ASocket: THandle): Integer; static; + + /// + /// ֹͣ׽(SD_RECEIVE=0, SD_SEND=1, SD_BOTH=2) + /// + class function Shutdown(ASocket: THandle; AHow: Integer = 2): Integer; static; + + /// + /// һ, Socket + /// + class function Accept(ASocket: THandle; Addr: PSockAddr; AddrLen: PInteger): THandle; static; + + /// + /// ׽ֵַָͶ˿, ֧ IPv6 + /// + class function Bind(ASocket: THandle; Addr: PSockAddr; AddrLen: Integer): Integer; static; + + /// + /// ӵ, ֧ IPv6 + /// + class function Connect(ASocket: THandle; Addr: PSockAddr; AddrLen: Integer): Integer; static; + + /// + /// + /// + class function Listen(ASocket: THandle; backlog: Integer = SOMAXCONN): Integer; overload; static; + + /// + /// + /// + class function Recv(ASocket: THandle; var Buf; len: Integer; flags: Integer = 0): Integer; static; + + /// + /// + /// + class function Send(ASocket: THandle; const Buf; len: Integer; flags: Integer = 0): Integer; static; + + /// + /// ݴַָ˿(UDP) + /// + class function RecvFrom(ASocket: THandle; const Addr: PSockAddr; + var AddrLen: Integer; var Buf; len: Integer; flags: Integer = 0): Integer; static; + + /// + /// ݵַָ˿(UDP) + /// + class function SendTo(ASocket: THandle; const Addr: PSockAddr; + AddrLen: Integer; const Buf; len: Integer; flags: Integer = 0): Integer; static; + + /// + /// ׽ֹԶЭַ + /// + class function GetPeerName(ASocket: THandle; Addr: PSockAddr; + var AddrLen: Integer): Integer; static; + + /// + /// ׽ֹıЭַ + /// + class function GetSockName(ASocket: THandle; Addr: PSockAddr; + var AddrLen: Integer): Integer; static; + + /// + /// ȡ׽ֲ + /// + class function GetSockOpt(ASocket: THandle; ALevel, AOptionName: Integer; + var AOptionValue; var AOptionLen: Integer): Integer; overload; static; + + /// + /// ȡ׽ֲ + /// + class function GetSockOpt(ASocket: THandle; ALevel, AOptionName: Integer; + var AOptionValue: T): Integer; overload; static; + + /// + /// ׽ֲ + /// + class function SetSockOpt(ASocket: THandle; ALevel, AOptionName: Integer; + const AOptionValue; AOptionLen: Integer): Integer; overload; static; + + /// + /// ׽ֲ + /// + class function SetSockOpt(ASocket: THandle; ALevel, AOptionName: Integer; + const AOptionValue: T): Integer; overload; static; + + /// + /// ׽ִ + /// + class function GetError(ASocket: THandle): Integer; static; + + /// + /// ÷ģʽ + /// + class function SetNonBlock(ASocket: THandle; ANonBlock: Boolean = True): Integer; static; + + /// + /// õַģʽ + /// + class function SetReUseAddr(ASocket: THandle; AReUseAddr: Boolean = True): Integer; static; + + /// + /// + /// + class function SetKeepAlive(ASocket: THandle; AIdleSeconds, AInterval, ACount: Integer): Integer; static; + + /// + /// TCP_NODELAY + /// + class function SetTcpNoDelay(ASocket: THandle; ANoDelay: Boolean = True): Integer; static; + + /// + /// ÷ͻС + /// + class function SetSndBuf(ASocket: THandle; ABufSize: Integer): Integer; static; + + /// + /// ýջС + /// + class function SetRcvBuf(ASocket: THandle; ABufSize: Integer): Integer; static; + + /// + /// Linger(closesocket(), ǻûʱ) + /// + class function SetLinger(ASocket: THandle; const AOnOff: Boolean; ALinger: Integer): Integer; static; + + /// + /// ù㲥SO_BROADCAST + /// + class function SetBroadcast(ASocket: THandle; ABroadcast: Boolean = True): Integer; static; + + /// + /// ýճʱ(λΪms) + /// + class function SetRecvTimeout(ASocket: THandle; ATimeout: Cardinal): Integer; static; + + /// + /// ÷ͳʱ(λΪms) + /// + class function SetSendTimeout(ASocket: THandle; ATimeout: Cardinal): Integer; static; + + /// + /// 鿴ն + /// ATimeout < 0 + /// ATimeout = 0 + /// ATimeout > 0 ȴʱʱ + /// + class function Readable(ASocket: THandle; ATimeout: Integer): Integer; static; + + /// + /// 鿴Ͷ + /// ATimeout < 0 + /// ATimeout = 0 + /// ATimeout > 0 ȴʱʱ + /// + class function Writeable(ASocket: THandle; ATimeout: Integer): Integer; static; + + /// + /// ѽյֽ + /// + class function RecvdCount(ASocket: THandle): Integer; static; + + /// + /// ַϢ, ֧ IPv6 + /// + class function GetAddrInfo(const AHostName, AServiceName: string; + const AHints: TRawAddrInfo): PRawAddrInfo; overload; static; + + /// + /// ַϢ, ֧ IPv6 + /// + class function GetAddrInfo(const AHostName: string; APort: Word; + const AHints: TRawAddrInfo): PRawAddrInfo; overload; static; + + /// + /// ͷ GetAddrInfo ص + /// + class procedure FreeAddrInfo(ARawAddrInfo: PRawAddrInfo); static; + + /// + /// SockAddr ṹн IP ˿, ֧ IPv6 + /// + class procedure ExtractAddrInfo(const AAddr: PSockAddr; AAddrLen: Integer; + var AIP: string; var APort: Word); static; + + /// + /// Ϊ IP ַ, ֧ IPv6 + /// + class function GetIpAddrByHost(const AHost: string): string; static; + + /// + /// ׽ǷЧ + /// + class function IsValidSocket(ASocket: THandle): Boolean; static; + end; + +implementation + +{ TSocketAPI } + +class function TSocketAPI.NewSocket(const ADomain, AType, + AProtocol: Integer): THandle; +begin + Result := + {$IFDEF POSIX} + Posix.SysSocket. + {$ELSE} + Net.Winsock2. + {$ENDIF} + socket(ADomain, AType, AProtocol); + + {$IFDEF DEBUG} + if not IsValidSocket(Result) then + RaiseLastOSError; + {$ENDIF} +end; + +class function TSocketAPI.NewTcp: THandle; +begin + Result := TSocketAPI.NewSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +end; + +class function TSocketAPI.NewUdp: THandle; +begin + Result := TSocketAPI.NewSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +end; + +class function TSocketAPI.Readable(ASocket: THandle; ATimeout: Integer): Integer; +var + {$IFDEF POSIX} + LFDSet: fd_set; + LTime_val: timeval; + {$ELSE} + LFDSet: TFDSet; + LTime_val: TTimeval; + {$ENDIF} + P: PTimeVal; +begin + if (ATimeout >= 0) then + begin + LTime_val.tv_sec := ATimeout div 1000; + LTime_val.tv_usec := 1000 * (ATimeout mod 1000); + P := @LTime_val; + end else + P := nil; + + {$IFDEF POSIX} + FD_ZERO(LFDSet); + _FD_SET(ASocket, LFDSet); + Result := Posix.SysSelect.select(0, @LFDSet, nil, nil, P); + {$ELSE} + FD_ZERO(LFDSet); + FD_SET(ASocket, LFDSet); + Result := Net.Winsock2.select(0, @LFDSet, nil, nil, P); + {$ENDIF} +end; + +class function TSocketAPI.Recv(ASocket: THandle; var Buf; len, + flags: Integer): Integer; +begin + Result := + {$IFDEF POSIX} + Posix.SysSocket. + {$ELSE} + Net.Winsock2. + {$ENDIF} + recv(ASocket, Buf, len, flags); +end; + +class function TSocketAPI.RecvdCount(ASocket: THandle): Integer; +{$IFNDEF POSIX} +var + LTemp : Cardinal; +{$ENDIF} +begin + {$IFDEF POSIX} + Result := ioctl(ASocket, FIONREAD); + {$ELSE} + if ioctlsocket(ASocket, FIONREAD, LTemp) = SOCKET_ERROR then + Result := -1 + else + Result := LTemp; + {$ENDIF} +end; + +class function TSocketAPI.RecvFrom(ASocket: THandle; const Addr: PSockAddr; + var AddrLen: Integer; var Buf; len, flags: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.recvfrom(ASocket, Buf, len, flags, Addr^, Cardinal(AddrLen)); + {$ELSE} + Result := Net.Winsock2.recvfrom(ASocket, Buf, len, flags, Addr, @AddrLen); + {$ENDIF} +end; + +class function TSocketAPI.Accept(ASocket: THandle; Addr: PSockAddr; + AddrLen: PInteger): THandle; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.accept(ASocket, Addr^, Cardinal(AddrLen^)); + {$ELSE} + Result := Net.Winsock2.accept(ASocket, Addr, AddrLen); + {$ENDIF} +end; + +class function TSocketAPI.Bind(ASocket: THandle; Addr: PSockAddr; + AddrLen: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.bind(ASocket, Addr^, AddrLen); + {$ELSE} + Result := Net.Winsock2.bind(ASocket, Addr, AddrLen); + {$ENDIF} +end; + +class function TSocketAPI.CloseSocket(ASocket: THandle): Integer; +begin + {$IFDEF POSIX} + Result := Posix.UniStd.__close(ASocket); + {$ELSE} + Result := Net.Winsock2.closesocket(ASocket); + {$ENDIF} +end; + +class function TSocketAPI.Shutdown(ASocket: THandle; AHow: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.shutdown(ASocket, AHow); + {$ELSE} + Result := Net.Winsock2.shutdown(ASocket, AHow); + {$ENDIF} +end; + +class function TSocketAPI.Connect(ASocket: THandle; Addr: PSockAddr; + AddrLen: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.connect(ASocket, Addr^, AddrLen); + {$ELSE} + Result := Net.Winsock2.connect(ASocket, Addr, AddrLen); + {$ENDIF} + + {$IFDEF DEBUG} +// if (Result <> 0) and (GetLastError <> EINPROGRESS) then +// RaiseLastOSError; + {$ENDIF} +end; + +class function TSocketAPI.GetAddrInfo(const AHostName, AServiceName: string; + const AHints: TRawAddrInfo): PRawAddrInfo; +var + M: TMarshaller; + LHost, LService: Pointer; + LRet: Integer; + LAddrInfo: PRawAddrInfo; +begin + Result := nil; + + {$IFDEF POSIX} + if (AHostName <> '') then + LHost := M.AsAnsi(AHostName).ToPointer + else + LHost := nil; + if (AServiceName <> '') then + LService := M.AsAnsi(AServiceName).ToPointer + else + LService := nil; + LRet := Posix.NetDB.getaddrinfo(LHost, LService, AHints, Paddrinfo(LAddrInfo)); + {$ELSE} + if (AHostName <> '') then + LHost := M.OutString(AHostName).ToPointer + else + LHost := nil; + if (AServiceName <> '') then + LService := M.OutString(AServiceName).ToPointer + else + LService := nil; + LRet := Net.Wship6.getaddrinfo(LHost, LService, @AHints, @LAddrInfo); + {$ENDIF} + + if (LRet <> 0) then Exit; + + Result := LAddrInfo; +end; + +class function TSocketAPI.GetAddrInfo(const AHostName: string; APort: Word; + const AHints: TRawAddrInfo): PRawAddrInfo; +begin + Result := GetAddrInfo(AHostName, APort.ToString, AHints); +end; + +class function TSocketAPI.GetError(ASocket: THandle): Integer; +var + LRet, LErrLen: Integer; +begin + LErrLen := SizeOf(Integer); + LRet := TSocketAPI.GetSockOpt(ASocket, SOL_SOCKET, SO_ERROR, Result, LErrLen); + if (LRet <> 0) then + Result := LRet; +end; + +class procedure TSocketAPI.FreeAddrInfo(ARawAddrInfo: PRawAddrInfo); +begin + {$IFDEF POSIX} + Posix.NetDB.freeaddrinfo(ARawAddrInfo^); + {$ELSE} + Net.Wship6.freeaddrinfo(PAddrInfoW(ARawAddrInfo)); + {$ENDIF} +end; + +class procedure TSocketAPI.ExtractAddrInfo(const AAddr: PSockAddr; + AAddrLen: Integer; var AIP: string; var APort: Word); +var + M: TMarshaller; + LIP, LServInfo: TPtrWrapper; +begin + LIP := M.AllocMem(NI_MAXHOST); + LServInfo := M.AllocMem(NI_MAXSERV); + {$IFDEF POSIX} + getnameinfo(AAddr^, AAddrLen, LIP.ToPointer, NI_MAXHOST, LServInfo.ToPointer, NI_MAXSERV, NI_NUMERICHOST or NI_NUMERICSERV); + AIP := TMarshal.ReadStringAsAnsi(LIP); + APort := TMarshal.ReadStringAsAnsi(LServInfo).ToInteger; + {$ELSE} + getnameinfo(AAddr, AAddrLen, LIP.ToPointer, NI_MAXHOST, LServInfo.ToPointer, NI_MAXSERV, NI_NUMERICHOST or NI_NUMERICSERV); + AIP := TMarshal.ReadStringAsUnicode(LIP); + APort := TMarshal.ReadStringAsUnicode(LServInfo).ToInteger; + {$ENDIF} +end; + +class function TSocketAPI.GetIpAddrByHost(const AHost: string): string; +var + LHints: TRawAddrInfo; + LAddrInfo: PRawAddrInfo; + LPort: Word; +begin + FillChar(LHints, SizeOf(TRawAddrInfo), 0); + LAddrInfo := GetAddrInfo(AHost, '', LHints); + if (LAddrInfo = nil) then Exit(''); + ExtractAddrInfo(LAddrInfo.ai_addr, LAddrInfo.ai_addrlen, Result, LPort); + FreeAddrInfo(LAddrInfo); +end; + +class function TSocketAPI.GetPeerName(ASocket: THandle; Addr: PSockAddr; + var AddrLen: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.getpeername(ASocket, Addr^, Cardinal(AddrLen)); + {$ELSE} + Result := Net.Winsock2.getpeername(ASocket, Addr, AddrLen); + {$ENDIF} +end; + +class function TSocketAPI.GetSockName(ASocket: THandle; Addr: PSockAddr; + var AddrLen: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.getsockname(ASocket, Addr^, Cardinal(AddrLen)); + {$ELSE} + Result := Net.Winsock2.getsockname(ASocket, Addr, AddrLen); + {$ENDIF} +end; + +class function TSocketAPI.GetSockOpt(ASocket: THandle; ALevel, AOptionName: Integer; + var AOptionValue; var AOptionLen: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.getsockopt(ASocket, ALevel, AOptionName, AOptionValue, Cardinal(AOptionLen)); + {$ELSE} + Result := Net.Winsock2.getsockopt(ASocket, ALevel, AOptionName, PAnsiChar(@AOptionValue), AOptionLen); + {$ENDIF} +end; + +class function TSocketAPI.GetSockOpt(ASocket: THandle; ALevel, + AOptionName: Integer; var AOptionValue: T): Integer; +var + LOptionLen: Integer; +begin + Result := GetSockOpt(ASocket, ALevel, AOptionName, AOptionValue, LOptionLen); +end; + +class function TSocketAPI.IsValidSocket(ASocket: THandle): Boolean; +begin + Result := (ASocket <> INVALID_HANDLE_VALUE); +end; + +class function TSocketAPI.Listen(ASocket: THandle; backlog: Integer): Integer; +begin + Result := + {$IFDEF POSIX} + Posix.SysSocket. + {$ELSE} + Net.Winsock2. + {$ENDIF} + listen(ASocket, backlog); + + {$IFDEF DEBUG} +// if (Result <> 0) then +// RaiseLastOSError; + {$ENDIF} +end; + +class function TSocketAPI.Send(ASocket: THandle; const Buf; len, + flags: Integer): Integer; +begin + Result := + {$IFDEF POSIX} + Posix.SysSocket. + {$ELSE} + Net.Winsock2. + {$ENDIF} + send(ASocket, Buf, len, flags); +end; + +class function TSocketAPI.SendTo(ASocket: THandle; const Addr: PSockAddr; + AddrLen: Integer; const Buf; len, flags: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.sendto(ASocket, Buf, len, flags, Addr^, AddrLen); + {$ELSE} + Result := Net.Winsock2.sendto(ASocket, Buf, len, flags, Addr, AddrLen); + {$ENDIF} +end; + +class function TSocketAPI.SetBroadcast(ASocket: THandle; + ABroadcast: Boolean): Integer; +var + LOptVal: Integer; +begin + if ABroadcast then + LOptVal := 1 + else + LOptVal := 0; + Result := TSocketAPI.SetSockOpt(ASocket, SOL_SOCKET, SO_BROADCAST, LOptVal, SizeOf(Integer)); +end; + +class function TSocketAPI.SetKeepAlive(ASocket: THandle; AIdleSeconds, + AInterval, ACount: Integer): Integer; +var + LOptVal: Integer; + {$IFDEF MSWINDOWS} + LKeepAlive: tcp_keepalive; + LBytes: Cardinal; + {$ENDIF} +begin + LOptVal := 1; + Result := SetSockOpt(ASocket, SOL_SOCKET, SO_KEEPALIVE, LOptVal, SizeOf(Integer)); + if (Result < 0) then Exit; + + {$IFDEF MSWINDOWS} + // Windows ԴΪ 3 , ޷޸ + LKeepAlive.onoff := 1; + LKeepAlive.keepalivetime := AIdleSeconds * 1000; + LKeepAlive.keepaliveinterval := AInterval * 1000; + LBytes := 0; + Result := WSAIoctl(ASocket, SIO_KEEPALIVE_VALS, @LKeepAlive, SizeOf(tcp_keepalive), + nil, 0, @LBytes, nil, nil); + {$ELSEIF defined(MACOS)} + // MAC TCP_KEEPALIVE ൱ Linux е TCP_KEEPIDLE + // ݲ֧ TCP_KEEPINTVL TCP_KEEPCNT + // OSX 10.9.5Ĭϵ + // sysctl -A | grep net.inet.tcp.*keep + // ************************************** + // net.inet.tcp.keepidle: 7200000 + // net.inet.tcp.keepintvl: 75000 + // net.inet.tcp.keepinit: 75000 + // net.inet.tcp.keepcnt: 8 + // net.inet.tcp.always_keepalive: 0 + // ************************************** + Result := SetSockOpt(ASocket, IPPROTO_TCP, TCP_KEEPALIVE, AIdleSeconds, SizeOf(Integer)); + {$ELSEIF defined(LINUX) or defined(ANDROID)} + Result := SetSockOpt(ASocket, IPPROTO_TCP, TCP_KEEPIDLE, AIdleSeconds, SizeOf(Integer)); + if (Result < 0) then Exit; + + Result := SetSockOpt(ASocket, IPPROTO_TCP, TCP_KEEPINTVL, AInterval, SizeOf(Integer)); + if (Result < 0) then Exit; + + Result := SetSockOpt(ASocket, IPPROTO_TCP, TCP_KEEPCNT, ACount, SizeOf(Integer)); + if (Result < 0) then Exit; + {$ENDIF} +end; + +class function TSocketAPI.SetLinger(ASocket: THandle; + const AOnOff: Boolean; ALinger: Integer): Integer; +var + LLinger: linger; +begin + if AOnOff then + LLinger.l_onoff := 1 + else + LLinger.l_onoff := 0; + LLinger.l_linger := ALinger; + Result := SetSockOpt(ASocket, SOL_SOCKET, SO_LINGER, LLinger, SizeOf(linger)); +end; + +class function TSocketAPI.SetNonBlock(ASocket: THandle; + ANonBlock: Boolean): Integer; +var + LFlag: Cardinal; +begin + {$IFDEF POSIX} + LFlag := fcntl(ASocket, F_GETFL); + if ANonBlock then + LFlag := LFlag and not O_SYNC or O_NONBLOCK + else + LFlag := LFlag and not O_NONBLOCK or O_SYNC; + Result := fcntl(ASocket, F_SETFL, LFlag); + {$ELSE} + if ANonBlock then + LFlag := 1 + else + LFlag := 0; + Result := ioctlsocket(ASocket, FIONBIO, LFlag); + {$ENDIF} +end; + +class function TSocketAPI.SetReUseAddr(ASocket: THandle; + AReUseAddr: Boolean): Integer; +var + LOptVal: Integer; +begin + if AReUseAddr then + LOptVal := 1 + else + LOptVal := 0; + Result := TSocketAPI.SetSockOpt(ASocket, SOL_SOCKET, SO_REUSEADDR, LOptVal, SizeOf(Integer)); +end; + +class function TSocketAPI.SetRcvBuf(ASocket: THandle; + ABufSize: Integer): Integer; +begin + Result := TSocketAPI.SetSockOpt(ASocket, SOL_SOCKET, SO_RCVBUF, ABufSize, SizeOf(Integer)); +end; + +class function TSocketAPI.SetRecvTimeout(ASocket: THandle; + ATimeout: Cardinal): Integer; +begin + Result := SetSockOpt(ASocket, + SOL_SOCKET, SO_RCVTIMEO, ATimeout, SizeOf(Cardinal)); +end; + +class function TSocketAPI.SetSendTimeout(ASocket: THandle; + ATimeout: Cardinal): Integer; +begin + Result := TSocketAPI.SetSockOpt(ASocket, + SOL_SOCKET, SO_SNDTIMEO, ATimeout, SizeOf(Cardinal)); +end; + +class function TSocketAPI.SetSndBuf(ASocket: THandle; + ABufSize: Integer): Integer; +begin + Result := TSocketAPI.SetSockOpt(ASocket, SOL_SOCKET, SO_SNDBUF, ABufSize, SizeOf(Integer)); +end; + +class function TSocketAPI.SetSockOpt(ASocket: THandle; ALevel, AOptionName: Integer; + const AOptionValue; AOptionLen: Integer): Integer; +begin + {$IFDEF POSIX} + Result := Posix.SysSocket.setsockopt(ASocket, ALevel, AOptionName, AOptionValue, Cardinal(AOptionLen)); + {$ELSE} + Result := Net.Winsock2.setsockopt(ASocket, ALevel, AOptionName, PAnsiChar(@AOptionValue), AOptionLen); + {$ENDIF} +end; + +class function TSocketAPI.SetSockOpt(ASocket: THandle; ALevel, + AOptionName: Integer; const AOptionValue: T): Integer; +begin + Result := SetSockOpt(ASocket, ALevel, AOptionName, AOptionValue, SizeOf(T)); +end; + +class function TSocketAPI.SetTcpNoDelay(ASocket: THandle; + ANoDelay: Boolean): Integer; +var + LOptVal: Integer; +begin + if ANoDelay then + LOptVal := 1 + else + LOptVal := 0; + Result := TSocketAPI.SetSockOpt(ASocket, IPPROTO_TCP, TCP_NODELAY, LOptVal, SizeOf(Integer)); +end; + +class function TSocketAPI.Writeable(ASocket: THandle; + ATimeout: Integer): Integer; +var + {$IFDEF POSIX} + LFDSet: fd_set; + LTime_val: timeval; + {$ELSE} + LFDSet: TFDSet; + LTime_val: TTimeval; + {$ENDIF} + P: PTimeVal; +begin + if (ATimeout >= 0) then + begin + LTime_val.tv_sec := ATimeout div 1000; + LTime_val.tv_usec := 1000 * (ATimeout mod 1000); + P := @LTime_val; + end else + P := nil; + + {$IFDEF POSIX} + FD_ZERO(LFDSet); + _FD_SET(ASocket, LFDSet); + Result := Posix.SysSelect.select(0, nil, @LFDSet, nil, P); + {$ELSE} + FD_ZERO(LFDSet); + FD_SET(ASocket, LFDSet); + Result := Net.Winsock2.select(0, nil, @LFDSet, nil, P); + {$ENDIF} +end; + +end. diff --git a/ThirdParty/DCS/Net/Net.Winsock.inc b/ThirdParty/DCS/Net/Net.Winsock.inc new file mode 100644 index 00000000..7141c294 --- /dev/null +++ b/ThirdParty/DCS/Net/Net.Winsock.inc @@ -0,0 +1,1049 @@ +// General + +// Make this $DEFINE to use the 16 color icons required by Borland +// or DEFINE to use the 256 color Indy versions +{.$DEFINE Borland} + +// S.G. 4/9/2002: IPv4/IPv6 general switch (for defaults only) +{$DEFINE IdIPv4} + +{$DEFINE INDY100} +{$DEFINE 10_5_8} //so developers can IFDEF for this specific version + +{$IFDEF BCB} + {$DEFINE CBUILDER} +{$ELSE} + {$DEFINE DELPHI} +{$ENDIF} + +{$UNDEF USE_OPENSSL} +{$UNDEF USE_ZLIB_UNIT} +{$UNDEF USE_SSPI} + +// $DEFINE the following if the global objects in the IdStack and IdThread +// units should be freed on finalization +{.$DEFINE FREE_ON_FINAL} +{$UNDEF FREE_ON_FINAL} + +// Make sure the following is $DEFINE'd only for suitable environments +// as specified further below. This works in conjunction with the +// FREE_ON_FINAL define above. +{$UNDEF REGISTER_EXPECTED_MEMORY_LEAK} + +// FastMM is natively available in BDS 2006 and higher. $DEFINE the +// following if FastMM has been installed manually +{.$DEFINE USE_FASTMM4} +{$UNDEF USE_FASTMM4} + +// Make sure the following is $DEFINE'd only for Delphi/C++Builder 2009 onwards +// as specified further below. The VCL is fully Unicode, where the 'String' +// type maps to System.UnicodeString, not System.AnsiString anymore +{$UNDEF STRING_IS_UNICODE} +{$UNDEF STRING_IS_ANSI} + +// Make sure the following is $DEFINE'd only for suitable environments +// as specified further below. This works in conjunction with the +// STRING_IS_ANSI and STRING_IS_UNICODE defines above. +{$UNDEF UNICODE_BUT_STRING_IS_ANSI} + +// Make sure the following are $DEFINE'd only for suitable environments +// as specified further below. +{$UNDEF HAS_TEncoding} +{$UNDEF HAS_TCharacter} +{$UNDEF HAS_TInterlocked} +{$UNDEF TIdTextEncoding_IS_NATIVE} + +// Make sure that this is defined only for environments where we are using +// the iconv library to charactor conversions. +{.$UNDEF USE_ICONV} + +//Define for Delphi cross-compiler targetting Posix +{$UNDEF USE_VCL_POSIX} + +// detect compiler versions + +// Delphi 4 +{$IFDEF VER120} + {$DEFINE DCC} + {$DEFINE VCL_40} + {$DEFINE DELPHI_4} +{$ENDIF} + +// C++Builder 4 +{$IFDEF VER125} + {$DEFINE DCC} + {$DEFINE VCL_40} + {$DEFINE CBUILDER_4} +{$ENDIF} + +// Delphi & C++Builder 5 +{$IFDEF VER130} + {$DEFINE DCC} + {$DEFINE VCL_50} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_5} + {$ELSE} + {$DEFINE DELPHI_5} + {$ENDIF} +{$ENDIF} + +//Delphi & C++Builder 6 +{$IFDEF VER140} + {$DEFINE DCC} + {$DEFINE VCL_60} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_6} + {$ELSE} + {$DEFINE DELPHI_6} + {$ENDIF} +{$ENDIF} + +//Delphi 7 +{$IFDEF VER150} + {$DEFINE DCC} + {$DEFINE VCL_70} + {$DEFINE DELPHI_7} // there was no C++ Builder 7 +{$ENDIF} + +//Delphi 8 +{$IFDEF VER160} + {$DEFINE DCC} + {$DEFINE VCL_80} + {$DEFINE DELPHI_8} // there was no C++ Builder 8 +{$ENDIF} + +//Delphi 2005 +{$IFDEF VER170} + {$DEFINE DCC} + {$DEFINE VCL_2005} + {$DEFINE DELPHI_2005} // there was no C++Builder 2005 +{$ENDIF} + +// NOTE: CodeGear decided to make Highlander be a non-breaking release +// (no interface changes, thus fully backwards compatible without any +// end user code changes), so VER180 applies to both BDS 2006 and +// Highlander prior to the release of RAD Studio 2007. Use VER185 to +// identify Highlanger specifically. + +//Delphi & C++Builder 2006 +//Delphi & C++Builder 2007 (Highlander) +{$IFDEF VER180} + {$DEFINE DCC} + {$DEFINE VCL_2006} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_2006} + {$ELSE} + {$DEFINE DELPHI_2006} + {$ENDIF} +{$ENDIF} + +//Delphi & C++Builder 2007 (Highlander) +{$IFDEF VER185} + {$DEFINE DCC} + {$UNDEF VCL_2006} + {$DEFINE VCL_2007} + {$IFDEF CBUILDER} + {$UNDEF CBUILDER_2006} + {$DEFINE CBUILDER_2007} + {$ELSE} + {$UNDEF DELPHI_2006} + {$DEFINE DELPHI_2007} + {$ENDIF} +{$ENDIF} + +// BDS 2007 NET personality uses VER190 instead of 185. +//Delphi .NET 2007 +{$IFDEF VER190} + {$DEFINE DCC} + {$IFDEF CIL} + //Delphi 2007 + {$DEFINE VCL_2007} + {$DEFINE DELPHI_2007} + {$ENDIF} +{$ENDIF} + +//Delphi & C++Builder 2009 (Tiburon) +{$IFDEF VER200} + {$DEFINE DCC} + {$DEFINE VCL_2009} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_2009} + {$ELSE} + {$DEFINE DELPHI_2009} + {$ENDIF} +{$ENDIF} + +//Delphi & C++Builder 2010 (Weaver) +{$IFDEF VER210} + {$DEFINE DCC} + {$DEFINE VCL_2010} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_2010} + {$ELSE} + {$DEFINE DELPHI_2010} + {$ENDIF} +{$ENDIF} + +//Delphi & C++Builder XE (Fulcrum) +{$IFDEF VER220} +//REMOVE DCC DEFINE after the next Fulcrum beta. +//It will be defined there. + {$DEFINE DCC} + {$DEFINE VCL_XE} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_XE} + {$ELSE} + {$DEFINE DELPHI_XE} + {$ENDIF} +{$ENDIF} + +//Delphi & CBuilder XE2 (Pulsar) +{$IFDEF VER230} + // DCC is now defined by the compiler + {$DEFINE VCL_XE2} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_XE2} + {$ELSE} + {$DEFINE DELPHI_XE2} + {$ENDIF} +{$ENDIF} + +//Delphi & CBuilder XE3 & Above +{$if COMPILERVERSION >= 24} + // DCC is now defined by the compiler + {$DEFINE VCL_XE2} + {$IFDEF CBUILDER} + {$DEFINE CBUILDER_XE2} + {$ELSE} + {$DEFINE DELPHI_XE2} + {$ENDIF} +{$ifend} + +// Delphi.NET +// Covers D8+ +{$IFDEF CIL} + // Platform specific conditional. Used for platform specific code. + {$DEFINE DOTNET} + {$DEFINE STRING_IS_UNICODE} +{$ENDIF} + +// Kylix +// +//Important: Don't use CompilerVersion here as IF's are evaluated before +//IFDEF's and Kylix 1 does not have CompilerVersion defined at all. +{$IFNDEF FPC} + {$IFDEF LINUX} + {$DEFINE UNIX} + {$IFDEF CONDITIONALEXPRESSIONS} + {$IF (RTLVersion >= 14.0) and (RTLVersion <= 14.5) } + {$DEFINE KYLIX} + {$IF RTLVersion = 14.5} + {$DEFINE KYLIX_3} + {$ELSEIF RTLVersion >= 14.2} + {$DEFINE KYLIX_2} + {$ELSE} + {$DEFINE KYLIX_1} + {$IFEND} + {$IFEND} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +{$IFDEF KYLIX} + {$DEFINE VCL_60} + {$DEFINE INT_THREAD_PRIORITY} + {$DEFINE CPUI386} + {$UNDEF USE_BASEUNIX} + + {$IFDEF KYLIX_3} + {$DEFINE KYLIX_3_OR_ABOVE} + {$ENDIF} + + {$IFDEF KYLIX_3_OR_ABOVE} + {$DEFINE KYLIX_2_OR_ABOVE} + {$ELSE} + {$IFDEF KYLIX_2} + {$DEFINE KYLIX_2_OR_ABOVE} + {$ENDIF} + {$ENDIF} + + {$IFDEF KYLIX_2_OR_ABOVE} + {$DEFINE KYLIX_1_OR_ABOVE} + {$ELSE} + {$IFDEF KYLIX_1} + {$DEFINE KYLIX_1_OR_ABOVE} + {$ENDIF} + {$ENDIF} + + {$IFNDEF KYLIX_3_OR_ABOVE} + {$DEFINE KYLIXCOMPAT} + {$ENDIF} + + {$IFDEF KYLIX_2_OR_ABOVE} + {$DEFINE USE_ZLIB_UNIT} + {$ENDIF} +{$ENDIF} + +// FPC (2+) + +{$IFDEF FPC} + {$MODE Delphi} + //note that we may need further defines for widget types depending on + //what we do and what platforms we support in FPC. + //I'll let Marco think about that one. + {$IFDEF UNIX} + {$DEFINE USE_BASEUNIX} + {$IFDEF LINUX} + //In Linux for I386, you can choose between a Kylix-libc API or + //the standard RTL Unix API. Just pass -dKYLIXCOMPAT to the FPC compiler. + //I will see what I can do about the Makefile. + {$IFDEF KYLIXCOMPAT} + {$IFDEF CPUI386} + {$UNDEF USE_BASEUNIX} + {$ENDIF} + {$ENDIF} + {$ENDIF} + {$IFDEF USE_BASEUNIX} + {$UNDEF KYLIXCOMPAT} + {$ENDIF} + {$ENDIF} + {$DEFINE VCL_70} + {$DEFINE DELPHI_7} + + // FPC_FULLVERSION was added in FPC 2.2.4 + // Have to use Declared() or else Delphi compiler chokes, since it + // evaluates $IF statements before $IFDEF statements... + + {$MACRO ON} // must be on in order to use versioning macros + {$IF DECLARED(FPC_FULLVERSION) and (FPC_FULLVERSION >= 20402)} + {$DEFINE FPC_2_4_2_OR_ABOVE} + {$IFEND} + + {$IFDEF FPC_2_4_2_OR_ABOVE} + {$DEFINE FPC_2_2_0_OR_ABOVE} + {$ELSE} + {$IFDEF VER2_2} + {$DEFINE FPC_2_2_0_OR_ABOVE} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +// end FPC + +{$IFDEF VCL_XE2} + {$DEFINE VCL_XE2_OR_ABOVE} +{$ENDIF} + +{$IFDEF VCL_XE2_OR_ABOVE} + {$DEFINE VCL_XE_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_XE} + {$DEFINE VCL_XE_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_XE_OR_ABOVE} + {$DEFINE VCL_2010_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_2010} + {$DEFINE VCL_2010_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2010_OR_ABOVE} + {$DEFINE VCL_2009_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_2009} + {$DEFINE VCL_2009_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2009_OR_ABOVE} + {$DEFINE VCL_2007_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_2007} + {$DEFINE VCL_2007_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2007_OR_ABOVE} + {$DEFINE VCL_2006_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_2006} + {$DEFINE VCL_2006_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2006_OR_ABOVE} + {$DEFINE VCL_2005_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_2005} + {$DEFINE VCL_2005_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2005_OR_ABOVE} + {$DEFINE VCL_8_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_80} + {$DEFINE VCL_8_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_8_OR_ABOVE} + {$DEFINE VCL_7_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_70} + {$DEFINE VCL_7_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_7_OR_ABOVE} + {$DEFINE VCL_6_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_60} + {$DEFINE VCL_6_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_6_OR_ABOVE} + {$DEFINE VCL_5_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_50} + {$DEFINE VCL_5_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_5_OR_ABOVE} + {$DEFINE VCL_4_OR_ABOVE} +{$ELSE} + {$IFDEF VCL_40} + {$DEFINE VCL_4_OR_ABOVE} + {$ENDIF} +{$ENDIF} + +// Normalize Delphi compiler defines to match FPC for consistency: +// +// CPU32 - any 32-bit CPU +// CPU64 - any 64-bit CPU +// WINDOWS - any Windows platform (32-bit, 64-bit, CE) +// WIN32 - Windows 32-bit +// WIN64 - Windows 64-bit +// WINCE - Windows CE +// +// Consult the "Free Pascal Programmer's Guide", Appendix G for the complete +// list of defines that are used. Do not work on this unless you understand +// what the FreePascal developers are doing. Not only do you have to +// descriminate with operating systems, but also with chip architectures +// are well. +// +// DCC Pulsar+ define the following values: +// ASSEMBLER +// DCC +// CONDITIONALEXPRESSIONS +// NATIVECODE +// UNICODE +// MACOS +// MACOS32 +// MACOS64 +// MSWINDOWS +// WIN32 +// WIN64 +// LINUX +// POSIX +// POSIX32 +// CPU386 +// CPUX86 +// CPUX64 +// +// Kylix defines the following values: +// LINUX +// (others??) +// + +{$IFNDEF FPC} + // TODO: We need to use ENDIAN_BIG for big endian chip architectures, + // such as 680x0, PowerPC, Sparc, and MIPS, once DCC supports them, + // provided it does not already define its own ENDIAN values by then... + {$DEFINE ENDIAN_LITTLE} + {$IFNDEF VCL_6_OR_ABOVE} + {$DEFINE MSWINDOWS} + {$ENDIF} + {$IFDEF MSWINDOWS} + {$DEFINE WINDOWS} + {$ENDIF} + // TODO: map Pulsar's non-Windows platform defines... + {$IFDEF VCL_XE2_OR_ABOVE} + {$IFDEF CPU386} + //any 32-bit CPU + {$DEFINE CPU32} + //Intel 386 compatible chip architecture + {$DEFINE CPUI386} + {$ENDIF} + {$IFDEF CPUX86} + {$DEFINE CPU32} + {$ENDIF} + {$IFDEF CPUX64} + //any 64-bit CPU + {$DEFINE CPU64} + //AMD64 compatible chip architecture + {$DEFINE CPUX86_64} //historical name for AMD64 + {$DEFINE CPUAMD64} + {$ENDIF} + {$ELSE} + {$IFNDEF DOTNET} + {$IFNDEF KYLIX} + {$DEFINE I386} + {$ENDIF} + {$ENDIF} + {$DEFINE CPU32} + {$ENDIF} +{$ENDIF} + +{$IFDEF DOTNET} + //differences in DotNET Framework versions. + {$IFDEF VCL_2007_OR_ABOVE} + {$DEFINE DOTNET_2} + {$DEFINE DOTNET_2_OR_ABOVE} + {$ELSE} + {$DEFINE DOTNET_1_1} + {$ENDIF} + {$DEFINE DOTNET_1_1_OR_ABOVE} + // Extra include used in D7 for testing. Remove later when all comps are + // ported. Used to selectively exclude non ported parts. Allowed in places + // IFDEFs are otherwise not permitted. + {$DEFINE DOTNET_EXCLUDE} +{$ENDIF} + +// Check for available features + +{$IFDEF VCL_5_OR_ABOVE} + {$IFNDEF FPC} + {$IFNDEF KYLIX} + {$DEFINE HAS_REMOVEFREENOTIFICATION} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_6_OR_ABOVE} + {$DEFINE HAS_TSelectionEditor} + {$DEFINE HAS_PCardinal} + {$DEFINE HAS_PByte} + {$DEFINE HAS_PWord} + {$DEFINE HAS_PPointer} + {$DEFINE HAS_TList_Assign} + {$DEFINE HAS_sLineBreak} + {$DEFINE HAS_RaiseLastOSError} + {$DEFINE HAS_SysUtils_IncludeExcludeTrailingPathDelimiter} + {$DEFINE HAS_SysUtils_DirectoryExists} + {$DEFINE HAS_UNIT_DateUtils} + {$DEFINE HAS_UNIT_Types} + {$DEFINE HAS_TryStrToInt} + {$DEFINE HAS_TryStrToInt64} + {$DEFINE HAS_ENUM_ELEMENT_VALUES} + {$IFNDEF FPC} + {$DEFINE HAS_TStringList_CaseSensitive} + {$IFNDEF KYLIX} + {$DEFINE HAS_DEPRECATED} + {$ENDIF} + {$ENDIF} + {$IFNDEF DOTNET} + //Widget defines are omitted in .NET + {$DEFINE VCL_60_PLUS} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_7_OR_ABOVE} + {$IFNDEF FPC} + {$DEFINE HAS_UInt64} + {$DEFINE HAS_NAMED_THREADS} + {$ENDIF} + {$DEFINE HAS_TFormatSettings} + {$IFNDEF VCL_70} + // not implemented in D7 + {$DEFINE HAS_STATIC_TThread_Queue} + {$ENDIF} + {$IFNDEF VCL_80} + // not implemented in D8 + {$DEFINE HAS_STATIC_TThread_Synchronize} + {$ENDIF} +{$ELSE} + {$IFDEF CBUILDER_6} + {$DEFINE HAS_NAMED_THREADS} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2006_OR_ABOVE} + {$DEFINE USE_INLINE} + {$DEFINE HAS_2PARAM_FileAge} + {$DEFINE HAS_System_RegisterExpectedMemoryLeak} + {$IFNDEF FREE_ON_FINAL} + {$IFNDEF DOTNET} + {$DEFINE REGISTER_EXPECTED_MEMORY_LEAK} + {$ENDIF} + {$ENDIF} +{$ELSE} + {$DEFINE HAS_InterlockedCompareExchange_Pointers} +{$ENDIF} + +{$IFDEF VCL_2007_OR_ABOVE} + {$IFNDEF CBUILDER_2007} + // class properties are broken in C++Builder 2007, causing AVs at compile-time + {$DEFINE HAS_CLASSPROPERTIES} + {$ENDIF} + // Native(U)Int existed but were buggy, so do not use them yet + {.$DEFINE HAS_NativeInt} + {.$DEFINE HAS_NativeUInt} + {$DEFINE HAS_StrToInt64Def} + {$DEFINE HAS_ULONG_PTR} + {$DEFINE HAS_PGUID} + {$DEFINE HAS_PPAnsiChar} + {$DEFINE HAS_CurrentYear} + {$IFNDEF DOTNET} + {$DEFINE HAS_TIMEUNITS} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_2009_OR_ABOVE} + {$IFNDEF DOTNET} + {$DEFINE STRING_IS_UNICODE} + {$DEFINE HAS_TEncoding} + {$DEFINE HAS_TCharacter} + {$DEFINE HAS_InterlockedCompareExchangePointer} + {$DEFINE HAS_WIDE_TCharArray} + {$IFDEF VCL_2009} + // TODO: need to differentiate between RTM and Update 1 + // FmtStr() is broken in RTM but was fixed in Update 1 + {$DEFINE BROKEN_FmtStr} + {$ENDIF} + {$IFNDEF VCL_XE_OR_ABOVE} + // TEncoding.GetEncoding() and TMBCSEncoding support for codepage 20127 + // have some bugs in 2009 and 2010 that were finally fixed in XE + {$DEFINE BROKEN_TEncoding_GetEncoding} + {$DEFINE BROKEN_TMBCSEncoding_CP20127} + {$ENDIF} + {$ENDIF} + {$DEFINE HAS_CLASSVARS} + {$DEFINE HAS_DEPRECATED_MSG} + {$DEFINE HAS_TBytes} + {$DEFINE HAS_NativeInt} + {$DEFINE HAS_NativeUInt} +{$ENDIF} + +{$IFDEF VCL_2010_OR_ABOVE} + {$DEFINE HAS_CLASSCONSTRUCTOR} + {$DEFINE HAS_CLASSDESTRUCTOR} + {$DEFINE HAS_DELAYLOAD} + {$DEFINE HAS_TStrings_ValueFromIndex} + {$DEFINE HAS_TThread_NameThreadForDebugging} + {$DEFINE DEPRECATED_TThread_SuspendResume} +{$ENDIF} + +{$IFDEF VCL_XE_OR_ABOVE} + {$DEFINE HAS_TFormatSettings_Object} + {$IFNDEF DOTNET} + {$DEFINE HAS_TInterlocked} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_XE2_OR_ABOVE} + {$DEFINE HAS_SIZE_T} +{$ENDIF} + +// Delphi XE+ cross-compiling +{$IFNDEF FPC} + {$IFDEF POSIX} + {$IF RTLVersion >= 22.0} + {$DEFINE UNIX} + {$UNDEF USE_BASEUNIX} + {$DEFINE VCL_CROSS_COMPILE} + {$DEFINE USE_VCL_POSIX} + {$IFEND} + {$ENDIF} + {$IFDEF LINUX} + {$IFDEF CONDITIONALEXPRESSIONS} + {$IF RTLVersion >= 22.0} + {$DEFINE VCL_CROSS_COMPILE} + {$DEFINE USE_VCL_POSIX} + {$IFEND} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +{$IFDEF VCL_CROSS_COMPILE} + {$UNDEF KYLIXCOMPAT} +{$ELSE} + {$IFDEF KYLIXCOMPAT} + {$linklib c} + {$ENDIF} +{$ENDIF} + +{$IFDEF FPC} + {$DEFINE USE_INLINE} + {$DEFINE USE_CLASSINLINE} + {$DEFINE USE_TBitBtn} //use Bit Buttons instead of Buttons + {$DEFINE FPC_REINTRODUCE_BUG} + {$DEFINE FPC_CIRCULAR_BUG} + {$DEFINE NO_REDECLARE} + {$DEFINE BYTE_COMPARE_SETS} + // FreePascal 2.2.0 has an overload for InterlockedCompareExchange that takes pointers. + {$IFDEF FPC_2_2_0_OR_ABOVE} + {$DEFINE HAS_InterlockedCompareExchange_Pointers} + {$ENDIF} + {$DEFINE HAS_PtrInt} + {$DEFINE HAS_PtrUInt} + {$DEFINE HAS_PGUID} + {$DEFINE HAS_LPGUID} + {$DEFINE HAS_PPAnsiChar} + {$DEFINE HAS_ENUM_ELEMENT_VALUES} + {$IFDEF WINDOWS} + {$DEFINE HAS_ULONG_PTR} + {$ENDIF} + {$DEFINE HAS_UNIT_ctypes} + // FreePascal 2.4.2 has size_t + {$IFDEF FPC_2_4_2_OR_ABOVE} + {$DEFINE HAS_SIZE_T} + {$ENDIF} +{$ENDIF} + +{$IFDEF DOTNET} + {$DEFINE WIDGET_WINFORMS} +{$ELSE} + {$DEFINE WIDGET_VCL_LIKE} // LCL included. + {$DEFINE WIDGET_VCL_LIKE_OR_KYLIX} + {$IFDEF FPC} + {$DEFINE WIDGET_LCL} + {$ELSE} + {$IFDEF KYLIX} + {$DEFINE WIDGET_KYLIX} + {$ELSE} + {$DEFINE WIDGET_VCL} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +// .NET and Delphi 2009+ support UNICODE strings natively! +// NOTE: Do not define UNICODE here. The compiler defines +// the symbol automatically. +{$IFNDEF STRING_IS_UNICODE} + {$DEFINE STRING_IS_ANSI} + {$IFDEF UNICODE} + {$DEFINE UNICODE_BUT_STRING_IS_ANSI} + {$ENDIF} +{$ENDIF} + +{$IFDEF WIN32} + {$DEFINE WIN32_OR_WIN64} +{$ENDIF} +{$IFDEF WIN64} + {$DEFINE WIN32_OR_WIN64} +{$ENDIF} + +{$IFDEF WIN32_OR_WIN64} + {$DEFINE USE_OPENSSL} + {$DEFINE USE_ZLIB_UNIT} + {$DEFINE USE_SSPI} + {$IFDEF STRING_IS_UNICODE} + {$DEFINE SSPI_UNICODE} + {$ENDIF} +{$ENDIF} + +// High-performance counters are not reliable on multi-core systems, and have +// been known to cause problems with TIdIOHandler.ReadLn() timeouts in Windows +// XP SP3, both 32-bit and 64-bit. Refer to these discussions for more info: +// +// http://www.virtualdub.org/blog/pivot/entry.php?id=106 +// http://blogs.msdn.com/oldnewthing/archive/2008/09/08/8931563.aspx +// +// Do not enable thus unless you know it will work correctly on your systems! +{$IFDEF WINDOWS} + {.$DEFINE USE_HI_PERF_COUNTER_FOR_TICKS} +{$ENDIF} + +{$IFDEF UNIX} + {$DEFINE USE_OPENSSL} + {$DEFINE USE_ZLIB_UNIT} +{$ENDIF} + +{$IFDEF FPC_REQUIRES_PROPER_ALIGNMENT} + {$DEFINE REQUIRES_PROPER_ALIGNMENT} +{$ENDIF} + +// +//iconv defines section. +{$DEFINE USE_ICONV_UNIT} +{$DEFINE USE_ICONV_ENC} +{$IFDEF UNIX} + {$DEFINE USE_ICONV} + {$IFDEF USE_BASEUNIX} + {$IFDEF FPC} + {$UNDEF USE_ICONV_UNIT} + {$ELSE} + {$UNDEF USE_ICONV_ENC} + {$ENDIF} + {$ENDIF} + {$IFDEF KYLIXCOMPAT} + //important!! Iconv functions are defined in the libc.pas Kylix compatible unit. + {$UNDEF USE_ICONV_ENC} + {$UNDEF USE_ICONV_UNIT} + {$ENDIF} +{$ENDIF} +{$IFDEF NETWARELIBC} + {$DEFINE USE_ICONV} + //important!!! iconv functions are defined in the libc.pas Novell Netware header. + //Do not define USE_ICONVUNIT + {$UNDEF USE_ICONV_UNIT} + {$UNDEF USE_ICONV_ENC} +{$ENDIF} +{$IFNDEF USE_ICONV} + {$UNDEF USE_ICONV_UNIT} + {$UNDEF USE_ICONV_ENC} +{$ENDIF} +{$UNDEF USE_SAFELOADLIBRARY} +{$IFDEF WINDOWS} + {$UNDEF USE_ICONV_ENC} + {$DEFINE USE_SAFELOADLIBRARY} +{$ENDIF} + +{$UNDEF USE_INVALIDATE_MOD_CACHE} +{$UNDEF USE_SAFELOADLIBRARY} +//This must come after the iconv defines because this compiler targets a Unix-like +//operating system. One key difference is that it does have a TEncoding class. +//If this comes before the ICONV defines, it creates problems. +//This also must go before the THandle size calculations. +{$IFDEF VCL_CROSS_COMPILE} + {$IFDEF MACOS} + {$DEFINE BSD} + {$DEFINE DARWIN} + {$DEFINE USE_SAFELOADLIBRARY} + {$DEFINE USE_INVALIDATE_MOD_CACHE} + {$ENDIF} + //important!!! iconv functions are defined in the libc.pas Novell Netware header. + //Do not define USE_ICONVUNIT + {$UNDEF USE_ICONV} + {$UNDEF USE_ICONV_UNIT} + {$UNDEF USE_ICONV_ENC} + {$DEFINE INT_THREAD_PRIORITY} +{$ENDIF} + +//IMPORTANT!!!! +// +//Do not remove this!!! This is to work around a conflict. In DCC, MACOS +//will mean OS X. In FreePascal, the DEFINE MACOS means MacIntosh System OS Classic. +{$IFDEF DCC} + {$IFDEF MACOS} + {$DEFINE DARWIN} + {$ENDIF} +{$ENDIF} +{$IFDEF FPC} + {$IFDEF MACOS} + {$DEFINE MACOS_CLASSIC} + {$ENDIF} +{$ENDIF} + +{ +BSD 4.4 introduced a minor API change. sa_family was changed from a 16bit +word to an 8 bit byte and an 8 bit byte field named sa_len was added. +} +//Place this only after DARWIN has been defined for Delphi MACOS +{$IFDEF FREEBSD} + {$DEFINE SOCK_HAS_SINLEN} +{$ENDIF} +{$IFDEF DARWIN} + {$DEFINE SOCK_HAS_SINLEN} +{$ENDIF} +{$IFDEF HAIKU} + {$DEFINE SOCK_HAS_SINLEN} +{$ENDIF} +{$IFDEF MORPHOS} + {$DEFINE SOCK_HAS_SINLEN} +{$ENDIF} + +// Do NOT remove these IFDEF's. They are here because InterlockedExchange +// only handles 32bit values. Some Operating Systems may have 64bit +// THandles. This is not always tied to the platform architecture. + +{$IFDEF AMIGA} + {$DEFINE THANDLE_CPUBITS} +{$ENDIF} +{$IFDEF ATARI} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF BEOS} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF BSD} + //I think BSD might handle FreeBSD, NetBSD, OpenBSD, and Darwin + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF EMBEDDED} + {$DEFINE THANDLE_CPUBITS} +{$ENDIF} +{$IFDEF EMX} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF GBA} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF GO32} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF HAIKU} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF LINUX} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF MACOS_CLASSIC} + {$DEFINE THANDLE_CPUBITS} +{$ENDIF} +{$IFDEF MORPHOS} + {$DEFINE THANDLE_CPUBITS} +{$ENDIF} +{$IFDEF NATIVENT} //Native NT for kernel level drivers + {$DEFINE THANDLE_CPUBITS} +{$ENDIF} +{$IFDEF NDS} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF NETWARE} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF NETWARELIBC} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF OS2} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF PALMOS} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF SOLARIS} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF SYMBIAN} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF WII} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF WATCOM} + {$DEFINE THANDLE_32} +{$ENDIF} +{$IFDEF WINDOWS} + {$DEFINE THANDLE_CPUBITS} +{$ENDIF} + +// end platform specific stuff for THandle size + +{$IFDEF THANDLE_CPUBITS} + {$IFDEF CPU64} + {$DEFINE THANDLE_64} + {$ELSE} + {$DEFINE THANDLE_32} + {$ENDIF} +{$ENDIF} + +{$IFDEF DOTNET} + {$DEFINE DOTNET_OR_ICONV} + {$DEFINE TIdTextEncoding_IS_NATIVE} +{$ELSE} + {$IFDEF HAS_TEncoding} + {$IFNDEF USE_ICONV} + {$DEFINE TIdTextEncoding_IS_NATIVE} + {$ENDIF} + {$ENDIF} +{$ENDIF} +{$IFDEF USE_ICONV} + {$DEFINE DOTNET_OR_ICONV} +{$ENDIF} + +{$UNDEF STREAM_SIZE_64} +{$IFDEF FPC} + {$DEFINE STREAM_SIZE_64} +{$ELSE} + {$IFDEF VCL_6_OR_ABOVE} + {$DEFINE STREAM_SIZE_64} + {$ENDIF} +{$ENDIF} + +{$IFNDEF FREE_ON_FINAL} + {$IFNDEF REGISTER_EXPECTED_MEMORY_LEAK} + {$IFDEF USE_FASTMM4} + {$DEFINE REGISTER_EXPECTED_MEMORY_LEAK} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +{$IFDEF REGISTER_EXPECTED_MEMORY_LEAK} + {$IFDEF DOTNET} + {$UNDEF REGISTER_EXPECTED_MEMORY_LEAK} + {$ENDIF} + {$IFDEF VCL_CROSS_COMPILE} + // RLebeau: should this be enabled for Windows, at least? + {$UNDEF REGISTER_EXPECTED_MEMORY_LEAK} + {$ENDIF} +{$ENDIF} + +{ +We must determine what the SocketType parameter is for the Socket function. +In DotNET, it's SocketType. In Kylix and the libc.pas Kylix-compatibility +library, it's a __socket_type. In BaseUnix, it's a C-type Integer. In Windows, +it's a LongInt. + +} +{$UNDEF SOCKETTYPE_IS_SOCKETTYPE} +{$UNDEF SOCKETTYPE_IS_CINT} +{$UNDEF SOCKETTYPE_IS___SOCKETTYPE} +{$UNDEF SOCKETTYPE_IS_LONGINT} +{$UNDEF SOCKETTYPE_IS_NUMERIC} +{$UNDEF SOCKET_LEN_IS_socklen_t} +{$IFDEF DOTNET} + {$DEFINE SOCKETTYPE_IS_SOCKETTYPE} +{$ENDIF} +{$IFDEF USE_BASEUNIX} + {$DEFINE SOCKETTYPE_IS_CINT} + {$DEFINE SOCKETTYPE_IS_NUMERIC} +{$ENDIF} +{$IFDEF KYLIXCOMPAT} + {$DEFINE SOCKETTYPE_IS___SOCKETTYPE} +{$ENDIF} +{$IFDEF USE_VCL_POSIX} + {$DEFINE SOCKETTYPE_IS_NUMERIC} + {$DEFINE SOCKETTYPE_IS_LONGINT} + {$DEFINE SOCKET_LEN_IS_socklen_t} +{$ENDIF} +{$IFDEF WINDOWS} + {$DEFINE SOCKETTYPE_IS_LONGINT} + {$DEFINE SOCKETTYPE_IS_NUMERIC} +{$ENDIF} +{$IFDEF OS2} + {$DEFINE SOCKETTYPE_IS_LONGINT} + {$DEFINE SOCKETTYPE_IS_NUMERIC} +{$ENDIF} +{$IFDEF NETWARE} + {$DEFINE SOCKETTYPE_IS_LONGINT} + {$DEFINE SOCKETTYPE_IS_NUMERIC} +{$ENDIF} + +{Take advantage of some TCP features specific to some stacks. +They work somewhat similarly but there's a key difference. +In Linux, TCP_CORK is turned on to send fixed packet sizes and +when turned-off (uncorked), any remaining data is sent. With +TCP_NOPUSH, this might not happen and remaining data is only sent +before disconnect.} +{$UNDEF HAS_TCP_NOPUSH} +{$UNDEF HAS_TCP_CORK} +{$IFDEF BSD} + {$DEFINE HAS_TCP_NOPUSH} +{$ENDIF} +{$IFDEF LINUX} + {$DEFINE HAS_TCP_CORK} +{$ENDIF} +{$IFDEF SOLARIS} + {$DEFINE HAS_TCP_CORK} +{$ENDIF} + +{$IFDEF DEBUG} + {$UNDEF USE_INLINE} +{$ENDIF} diff --git a/ThirdParty/DCS/Net/Net.Winsock2.pas b/ThirdParty/DCS/Net/Net.Winsock2.pas new file mode 100644 index 00000000..1261e0ef --- /dev/null +++ b/ThirdParty/DCS/Net/Net.Winsock2.pas @@ -0,0 +1,6755 @@ +{ + $Project$ + $Workfile$ + $Revision$ + $DateUTC$ + $Id$ + + This file is part of the Indy (Internet Direct) project, and is offered + under the dual-licensing agreement described on the Indy website. + (http://www.indyproject.org/) + + Copyright: + (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. +} +{ + $Log$ +} +{ + Log: 56400: IdWinsock2.pas + + Rev 1.0 2004.02.03 3:14:50 PM czhower + Move and updates + + + Rev 1.15 1/3/2004 12:41:48 AM BGooijen + Fixed WSAEnumProtocols + + + Rev 1.14 10/15/2003 1:20:48 PM DSiders + Added localization comments. + + + Rev 1.13 2003.10.01 11:16:38 AM czhower + .Net + + + Rev 1.12 9/24/2003 09:18:24 AM JPMugaas + Fixed an AV that happened when a stack call was made. + + + Rev 1.11 24/9/2003 3:11:34 PM SGrobety + First wave of fixes for compiling in dotnet. Still not functional, needed to + unlock to fix critical failure in Delphi code + + + Rev 1.10 9/22/2003 11:20:14 PM EHill + Removed assembly code and replaced with defined API stubs. + + + Rev 1.9 7/7/2003 12:55:10 PM BGooijen + Fixed ServiceQueryTransmitFile, and made it public + + + Rev 1.8 2003.05.09 10:59:30 PM czhower + + + Rev 1.7 4/19/2003 10:28:24 PM BGooijen + some functions were linked to the wrong dll + + + Rev 1.6 4/19/2003 11:14:40 AM JPMugaas + Made some tentitive wrapper functions for some things that should be called + from the Service Provider. Fixed WSARecvMsg. + + + Rev 1.5 4/19/2003 02:29:26 AM JPMugaas + Added TransmitPackets API function call. Note that this is only supported in + Winapi.Windows XP or later. + + + Rev 1.4 4/19/2003 12:22:58 AM BGooijen + fixed: ConnectEx DisconnectEx WSARecvMsg + + + Rev 1.3 4/18/2003 12:00:58 AM JPMugaas + added + ConnectEx + DisconnectEx + WSARecvMsg + + Changed header procedure type names to be consistant with the old + IdWinsock.pas in Indy 8.0 and with the rest of the unit. + + + Rev 1.2 3/22/2003 10:01:26 PM JPMugaas + WSACreateEvent couldn't load because of a space. + + + Rev 1.1 3/22/2003 09:46:54 PM JPMugaas + It turns out that we really do not need the TGUID defination in the header at + all. It's defined in D4, D5, D6, and D7. + + + Rev 1.0 11/13/2002 09:02:54 AM JPMugaas +} +//------------------------------------------------------------- +// +// Borland Delphi Runtime Library +// interface unit +// +// Portions created by Microsoft are +// Copyright (C) 1995-1999 Microsoft Corporation. +// All Rights Reserved. +// +// The original file is: Winsock2.h from CBuilder5 distribution. +// The original Pascal code is: winsock2.pas, released 03 Mar 2001. +// The initial developer of the Pascal code is Alex Konshin +// (alexk@mtgroup.ru). +//------------------------------------------------------------- + + +{ Winsock2.h -- definitions to be used with the WinSock 2 DLL and WinSock 2 applications. + This header file corresponds to version 2.2.x of the WinSock API specification. + This file includes parts which are Copyright (c) 1982-1986 Regents + of the University of California. All rights reserved. + The Berkeley Software License Agreement specifies the terms and + conditions for redistribution. } + +// Note that the original unit is copyrighted by the original author and I did obtain his +// permission to port and use this as part of Indy - J. Peter Mugaas + +// 2002-01-28 - Hadi Hariri. Fixes for C++ Builder. Thanks to Chuck Smith. +// 2001 - Oct -25 J. Peter Mugaas +// Made adjustments for Indy usage by +// 1) including removing Trace logging +// 2) renaming and consolidating some .INC files as appropriate +// 3) modifying the unit to follow Indy conventions +// 4) Adding TransmitFile support for the HTTP Server +// 5) Removing all static loading code that was IFDEF'ed. {Do not Localize} +// 2001 - Mar - 1 Alex Konshin +// Revision 3 +// converted by Alex Konshin, mailto:alexk@mtgroup.ru +// revision 3, March,1 2001 + + +unit Net.Winsock2; + +interface + +{$I Net.Winsock.inc} + +{ +Important!!! + +With the ARM architecture, you may get an EBusError exception sating that +data is misaligned. Sometimes, that architecture does not have the ability to +read misaligned data. On an i386 and x86_64 architecure, you can do this but it +is inefficient. For the ARM chip architecture, we have to make sure our records +are aligned on a 4 byte boundery. See: + +http://wiki.lazarus.freepascal.org/Windows_CE_Development_Notes + +This is not necessary and can cause problems +when using the standard Win32 API (win32 and win64) where records are packed +instead of aligned. + +To deal with this, I use the FPC predefined FPC_REQUIRES_PROPER_ALIGNMENT. + +} + +{$RANGECHECKS OFF} +{$IFDEF FPC} + {$IFDEF WIN32} + {$ALIGN OFF} + {$ELSE} + //It turns out that Win64 and WinCE require record alignment + {$PACKRECORDS C} + {$ENDIF} +{$ELSE} + {$IFDEF WIN64} + {$ALIGN ON} + {$MINENUMSIZE 4} + {$ELSE} + {$MINENUMSIZE 4} + {$IFDEF REQUIRES_PROPER_ALIGNMENT} + {$ALIGN ON} + {$ELSE} + {$ALIGN OFF} + {$WRITEABLECONST OFF} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +uses + System.SysUtils, + System.AnsiStrings, + Winapi.Windows; + +type + EIdWinsockStubError = class(Exception) + protected + FWin32Error : DWORD; + FWin32ErrorMessage : String; + FTitle : String; + public + constructor Build(AWin32Error: DWORD; const ATitle: String; AArgs: array of const); + property Win32Error : DWORD read FWin32Error; + property Win32ErrorMessage : String read FWin32ErrorMessage; + property Title : String read FTitle; + end; + +const + {$IFDEF WINCE} + WINSOCK2_DLL = 'ws2.dll'; {Do not Localize} + {$ELSE} + WINSOCK2_DLL = 'WS2_32.DLL'; {Do not Localize} + MSWSOCK_DLL = 'MSWSOCK.DLL'; {Do not Localize} + {$ENDIF} + {$EXTERNALSYM WINSOCK_VERSION} + WINSOCK_VERSION = $0202; + +{$DEFINE WS2_DLL_FUNC_VARS} +{$DEFINE INCL_WINSOCK_API_PROTOTYPES} +{$DEFINE INCL_WINSOCK_API_TYPEDEFS} + +type + {$EXTERNALSYM u_char} + u_char = Byte; + {$EXTERNALSYM u_short} + u_short = Word; + {$EXTERNALSYM u_int} + u_int = DWord; //Integer; + {$EXTERNALSYM u_long} + u_long = DWORD; +// The new type to be used in all instances which refer to sockets. + {$EXTERNALSYM TSocket} + TSocket = UINT_PTR; + {$EXTERNALSYM WSAEVENT} + WSAEVENT = THandle; + {$NODEFINE PWSAEVENT} + PWSAEVENT = ^WSAEVENT; + {$EXTERNALSYM LPWSAEVENT} + LPWSAEVENT = PWSAEVENT; + + {$IFNDEF HAS_ULONG_PTR} + {$EXTERNALSYM ULONG_PTR} + ULONG_PTR = UINT_PTR; + {$ENDIF} + +const + {$EXTERNALSYM FD_SETSIZE} + FD_SETSIZE = 64; + +// the following emits are a workaround to the name conflicts +// with the winsock2 header files +(*$HPPEMIT '#include '*) +(*$HPPEMIT '#include '*) +(*$HPPEMIT '#include '*) +(*$HPPEMIT '// workaround for a bug in wsnwlink.h where a couple of commented lines are not terminated property'*) +(*$HPPEMIT '#pragma option push -C-'*) +(*$HPPEMIT '#include '*) +(*$HPPEMIT '#pragma option pop'*) +(*$HPPEMIT '#include '*) +(*$HPPEMIT '#include '*) +(*$HPPEMIT '#include '*) +(*$HPPEMIT ''*) +(*$HPPEMIT 'namespace Idwinsock2'*) +(*$HPPEMIT '{'*) +(*$HPPEMIT ' typedef fd_set *PFDSet;'*) // due to name conflict with procedure FD_SET +(*$HPPEMIT ' typedef fd_set TFDSet;'*) // due to name conflict with procedure FD_SET +(*$HPPEMIT '}'*) +(*$HPPEMIT ''*) + +type + {$NODEFINE PFDSet} + PFDSet = ^TFDSet; + {$NODEFINE TFDSet} + TFDSet = record + fd_count: u_int; + fd_array: array[0..FD_SETSIZE-1] of TSocket; + end; + + {$EXTERNALSYM timeval} + timeval = record + tv_sec: Longint; + tv_usec: Longint; + end; + {$NODEFINE TTimeVal} + TTimeVal = timeval; + {$NODEFINE PTimeVal} + PTimeVal = ^TTimeVal; + +const + {$EXTERNALSYM IOCPARM_MASK} + IOCPARM_MASK = $7F; + {$EXTERNALSYM IOC_VOID} + IOC_VOID = $20000000; + {$EXTERNALSYM IOC_OUT} + IOC_OUT = $40000000; + {$EXTERNALSYM IOC_IN} + IOC_IN = $80000000; + {$EXTERNALSYM IOC_INOUT} + IOC_INOUT = (IOC_IN or IOC_OUT); + +// get # bytes to read + {$EXTERNALSYM FIONREAD} + FIONREAD = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('f') shl 8) or 127; {Do not Localize} +// set/clear non-blocking i/o + {$EXTERNALSYM FIONBIO} + FIONBIO = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('f') shl 8) or 126; {Do not Localize} +// set/clear async i/o + {$EXTERNALSYM FIOASYNC} + FIOASYNC = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('f') shl 8) or 125; {Do not Localize} + +// Socket I/O Controls + +// set high watermark + {$EXTERNALSYM SIOCSHIWAT} + SIOCSHIWAT = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('s') shl 8) or 0; {Do not Localize} +// get high watermark + {$EXTERNALSYM SIOCGHIWAT} + SIOCGHIWAT = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('s') shl 8) or 1; {Do not Localize} +// set low watermark + {$EXTERNALSYM SIOCSLOWAT} + SIOCSLOWAT = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('s') shl 8) or 2; {Do not Localize} +// get low watermark + {$EXTERNALSYM SIOCGLOWAT} + SIOCGLOWAT = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('s') shl 8) or 3; {Do not Localize} +// at oob mark? + {$EXTERNALSYM SIOCATMARK} + SIOCATMARK = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('s') shl 8) or 7; {Do not Localize} + + +// Structures returned by network data base library, taken from the +// BSD file netdb.h. All addresses are supplied in host order, and +// returned in network order (suitable for use in system calls). +type + {$EXTERNALSYM hostent} + hostent = record + h_name: PAnsiChar; // official name of host + h_aliases: ^PAnsiChar; // alias list + h_addrtype: Smallint; // host address type + h_length: Smallint; // length of address + case Byte of + 0: (h_address_list: ^PAnsiChar); + 1: (h_addr: PAnsiChar); // address, for backward compat + end; + {$NODEFINE THostEnt} + THostEnt = hostent; + {$NODEFINE PHostEnt} + PHostEnt = ^THostEnt; + +// It is assumed here that a network number +// fits in 32 bits. + {$EXTERNALSYM netent} + netent = record + n_name: PAnsiChar; // official name of net + n_aliases: ^PAnsiChar; // alias list + n_addrtype: Smallint; // net address type + n_net: u_long; // network # + end; + {$NODEFINE TNetEnt} + TNetEnt = netent; + {$NODEFINE PNetEnt} + PNetEnt = ^TNetEnt; + + {$EXTERNALSYM servent} + servent = record + s_name: PAnsiChar; // official service name + s_aliases: ^PAnsiChar; // alias list + {$IFDEF _WIN64} + s_proto: PAnsiChar; // protocol to use + s_port: Smallint; // port # + {$ELSE} + s_port: Smallint; // port # + s_proto: PAnsiChar; // protocol to use + {$ENDIF} + end; + {$NODEFINE TServEnt} + TServEnt = servent; + {$NODEFINE PServEnt} + PServEnt = ^TServEnt; + + {$EXTERNALSYM protoent} + protoent = record + p_name: PAnsiChar; // official protocol name + p_aliases: ^PAnsiChar; // alias list + p_proto: Smallint; // protocol # + end; + {$NODEFINE TProtoEnt} + TProtoEnt = protoent; + {$NODEFINE PProtoEnt} + PProtoEnt = ^TProtoEnt; + +// Constants and structures defined by the internet system, +// Per RFC 790, September 1981, taken from the BSD file netinet/in.h. +const + +// Protocols + {$EXTERNALSYM IPPROTO_IP} + IPPROTO_IP = 0; // dummy for IP + {$EXTERNALSYM IPPROTO_ICMP} + IPPROTO_ICMP = 1; // control message protocol + {$EXTERNALSYM IPPROTO_IGMP} + IPPROTO_IGMP = 2; // group management protocol + {$EXTERNALSYM IPPROTO_GGP} + IPPROTO_GGP = 3; // gateway^2 (deprecated) + {$EXTERNALSYM IPPROTO_TCP} + IPPROTO_TCP = 6; // TCP + {$EXTERNALSYM IPPROTO_PUP} + IPPROTO_PUP = 12; // pup + {$EXTERNALSYM IPPROTO_UDP} + IPPROTO_UDP = 17; // UDP - user datagram protocol + {$EXTERNALSYM IPPROTO_IDP} + IPPROTO_IDP = 22; // xns idp + {$EXTERNALSYM IPPROTO_ND} + IPPROTO_ND = 77; // UNOFFICIAL net disk proto + + {$EXTERNALSYM IPPROTO_IPV6} + IPPROTO_IPV6 = 41; // IPv6 + {$EXTERNALSYM IPPROTO_ICLFXBM} + IPPROTO_ICLFXBM = 78; + + {$EXTERNALSYM IPPROTO_ICMPV6} + IPPROTO_ICMPV6 = 58; // control message protocol + + {$EXTERNALSYM IPPROTO_RAW} + IPPROTO_RAW = 255; // raw IP packet + {$EXTERNALSYM IPPROTO_MAX} + IPPROTO_MAX = 256; + +// Port/socket numbers: network standard functions + {$EXTERNALSYM IPPORT_ECHO} + IPPORT_ECHO = 7; + {$EXTERNALSYM IPPORT_DISCARD} + IPPORT_DISCARD = 9; + {$EXTERNALSYM IPPORT_SYSTAT} + IPPORT_SYSTAT = 11; + {$EXTERNALSYM IPPORT_DAYTIME} + IPPORT_DAYTIME = 13; + {$EXTERNALSYM IPPORT_NETSTAT} + IPPORT_NETSTAT = 15; + {$EXTERNALSYM IPPORT_FTP} + IPPORT_FTP = 21; + {$EXTERNALSYM IPPORT_TELNET} + IPPORT_TELNET = 23; + {$EXTERNALSYM IPPORT_SMTP} + IPPORT_SMTP = 25; + {$EXTERNALSYM IPPORT_TIMESERVER} + IPPORT_TIMESERVER = 37; + {$EXTERNALSYM IPPORT_NAMESERVER} + IPPORT_NAMESERVER = 42; + {$EXTERNALSYM IPPORT_WHOIS} + IPPORT_WHOIS = 43; + {$EXTERNALSYM IPPORT_MTP} + IPPORT_MTP = 57; + +// Port/socket numbers: host specific functions + {$EXTERNALSYM IPPORT_TFTP} + IPPORT_TFTP = 69; + {$EXTERNALSYM IPPORT_RJE} + IPPORT_RJE = 77; + {$EXTERNALSYM IPPORT_FINGER} + IPPORT_FINGER = 79; + {$EXTERNALSYM ipport_ttylink} + IPPORT_TTYLINK = 87; + {$EXTERNALSYM IPPORT_SUPDUP} + IPPORT_SUPDUP = 95; + +// UNIX TCP sockets + {$EXTERNALSYM IPPORT_EXECSERVER} + IPPORT_EXECSERVER = 512; + {$EXTERNALSYM IPPORT_LOGINSERVER} + IPPORT_LOGINSERVER = 513; + {$EXTERNALSYM IPPORT_CMDSERVER} + IPPORT_CMDSERVER = 514; + {$EXTERNALSYM IPPORT_EFSSERVER} + IPPORT_EFSSERVER = 520; + +// UNIX UDP sockets + {$EXTERNALSYM IPPORT_BIFFUDP} + IPPORT_BIFFUDP = 512; + {$EXTERNALSYM IPPORT_WHOSERVER} + IPPORT_WHOSERVER = 513; + {$EXTERNALSYM IPPORT_ROUTESERVER} + IPPORT_ROUTESERVER = 520; + +// Ports < IPPORT_RESERVED are reserved for privileged processes (e.g. root). + {$EXTERNALSYM IPPORT_RESERVED} + IPPORT_RESERVED = 1024; + + {$EXTERNALSYM IPPORT_REGISTERED_MIN} + IPPORT_REGISTERED_MIN = IPPORT_RESERVED; + + {$IFNDEF WINCE} + {$EXTERNALSYM IPPORT_REGISTERED_MAX} + IPPORT_REGISTERED_MAX = $bfff; + {$EXTERNALSYM IPPORT_DYNAMIC_MIN} + IPPORT_DYNAMIC_MIN = $c000; + {$EXTERNALSYM IPPORT_DYNAMIC_MAX} + IPPORT_DYNAMIC_MAX = $ffff; + {$ENDIF} + +// Link numbers + {$EXTERNALSYM IMPLINK_IP} + IMPLINK_IP = 155; + {$EXTERNALSYM IMPLINK_LOWEXPER} + IMPLINK_LOWEXPER = 156; + {$EXTERNALSYM IMPLINK_HIGHEXPER} + IMPLINK_HIGHEXPER = 158; + + {$EXTERNALSYM TF_DISCONNECT} + TF_DISCONNECT = $01; + {$EXTERNALSYM TF_REUSE_SOCKET} + TF_REUSE_SOCKET = $02; + {$EXTERNALSYM TF_WRITE_BEHIND} + TF_WRITE_BEHIND = $04; + {$EXTERNALSYM TF_USE_DEFAULT_WORKER} + TF_USE_DEFAULT_WORKER = $00; + {$EXTERNALSYM TF_USE_SYSTEM_THREAD} + TF_USE_SYSTEM_THREAD = $10; + {$EXTERNALSYM TF_USE_KERNEL_APC} + TF_USE_KERNEL_APC = $20; + +// This is used instead of -1, since the TSocket type is unsigned. + {$EXTERNALSYM INVALID_SOCKET} + INVALID_SOCKET = TSocket(not(0)); + {$EXTERNALSYM SOCKET_ERROR} + SOCKET_ERROR = -1; + +// The following may be used in place of the address family, socket type, or +// protocol in a call to WSASocket to indicate that the corresponding value +// should be taken from the supplied WSAPROTOCOL_INFO structure instead of the +// parameter itself. + {$EXTERNALSYM FROM_PROTOCOL_INFO} + FROM_PROTOCOL_INFO = -1; + + +// Types + {$EXTERNALSYM SOCK_STREAM} + SOCK_STREAM = 1; { stream socket } + {$EXTERNALSYM SOCK_DGRAM} + SOCK_DGRAM = 2; { datagram socket } + {$EXTERNALSYM SOCK_RAW} + SOCK_RAW = 3; { raw-protocol interface } + {$EXTERNALSYM SOCK_RDM} + SOCK_RDM = 4; { reliably-delivered message } + {$EXTERNALSYM SOCK_SEQPACKET} + SOCK_SEQPACKET = 5; { sequenced packet stream } + +// option flags per-socket. + {$EXTERNALSYM SO_DEBUG} + SO_DEBUG = $0001; // turn on debugging info recording + {$EXTERNALSYM SO_ACCEPTCONN} + SO_ACCEPTCONN = $0002; // socket has had listen() + {$EXTERNALSYM SO_REUSEADDR} + SO_REUSEADDR = $0004; // allow local address reuse + {$EXTERNALSYM SO_KEEPALIVE} + SO_KEEPALIVE = $0008; // keep connections alive + {$EXTERNALSYM SO_DONTROUTE} + SO_DONTROUTE = $0010; // just use interface addresses + {$EXTERNALSYM SO_BROADCAST} + SO_BROADCAST = $0020; // permit sending of broadcast msgs + {$EXTERNALSYM SO_USELOOPBACK} + SO_USELOOPBACK = $0040; // bypass hardware when possible + {$EXTERNALSYM SO_LINGER} + SO_LINGER = $0080; // linger on close if data present + {$EXTERNALSYM SO_OOBINLINE} + SO_OOBINLINE = $0100; // leave received OOB data in line + + {$EXTERNALSYM SO_DONTLINGER} + SO_DONTLINGER = not SO_LINGER; + {$EXTERNALSYM SO_EXCLUSIVEADDRUSE} + SO_EXCLUSIVEADDRUSE = not SO_REUSEADDR; // disallow local address reuse + +// additional options. + + {$EXTERNALSYM SO_SNDBUF} + SO_SNDBUF = $1001; // send buffer size + {$EXTERNALSYM SO_RCVBUF} + SO_RCVBUF = $1002; // receive buffer size + {$EXTERNALSYM SO_SNDLOWAT} + SO_SNDLOWAT = $1003; // send low-water mark + {$EXTERNALSYM SO_RCVLOWAT} + SO_RCVLOWAT = $1004; // receive low-water mark + {$EXTERNALSYM SO_SNDTIMEO} + SO_SNDTIMEO = $1005; // send timeout + {$EXTERNALSYM SO_RCVTIMEO} + SO_RCVTIMEO = $1006; // receive timeout + {$EXTERNALSYM SO_ERROR} + SO_ERROR = $1007; // get error status and clear + {$EXTERNALSYM SO_TYPE} + SO_TYPE = $1008; // get socket type + +// options for connect and disconnect data and options. +// used only by non-tcp/ip transports such as DECNet, OSI TP4, etc. + {$EXTERNALSYM SO_CONNDATA} + SO_CONNDATA = $7000; + {$EXTERNALSYM SO_CONNOPT} + SO_CONNOPT = $7001; + {$EXTERNALSYM SO_DISCDATA} + SO_DISCDATA = $7002; + {$EXTERNALSYM SO_DISCOPT} + SO_DISCOPT = $7003; + {$EXTERNALSYM SO_CONNDATALEN} + SO_CONNDATALEN = $7004; + {$EXTERNALSYM SO_CONNOPTLEN} + SO_CONNOPTLEN = $7005; + {$EXTERNALSYM SO_DISCDATALEN} + SO_DISCDATALEN = $7006; + {$EXTERNALSYM SO_DISCOPTLEN} + SO_DISCOPTLEN = $7007; + +// option for opening sockets for synchronous access. + {$EXTERNALSYM SO_OPENTYPE} + SO_OPENTYPE = $7008; + {$EXTERNALSYM SO_SYNCHRONOUS_ALERT} + SO_SYNCHRONOUS_ALERT = $10; + {$EXTERNALSYM SO_SYNCHRONOUS_NONALERT} + SO_SYNCHRONOUS_NONALERT = $20; + +// other nt-specific options. + {$EXTERNALSYM SO_MAXDG} + SO_MAXDG = $7009; + {$EXTERNALSYM SO_MAXPATHDG} + SO_MAXPATHDG = $700A; + {$EXTERNALSYM SO_UPDATE_ACCEPT_CONTEXT} + SO_UPDATE_ACCEPT_CONTEXT = $700B; + {$EXTERNALSYM SO_CONNECT_TIME} + SO_CONNECT_TIME = $700C; + +// tcp options. + {$EXTERNALSYM TCP_NODELAY} + TCP_NODELAY = $0001; + {$EXTERNALSYM TCP_BSDURGENT} + TCP_BSDURGENT = $7000; + +// winsock 2 extension -- new options + {$EXTERNALSYM SO_GROUP_ID} + SO_GROUP_ID = $2001; // ID of a socket group + {$EXTERNALSYM SO_GROUP_PRIORITY} + SO_GROUP_PRIORITY = $2002; // the relative priority within a group + {$EXTERNALSYM SO_MAX_MSG_SIZE} + SO_MAX_MSG_SIZE = $2003; // maximum message size + {$EXTERNALSYM SO_PROTOCOL_INFOA} + SO_PROTOCOL_INFOA = $2004; // WSAPROTOCOL_INFOA structure + {$EXTERNALSYM SO_PROTOCOL_INFOW} + SO_PROTOCOL_INFOW = $2005; // WSAPROTOCOL_INFOW structure + {$EXTERNALSYM SO_PROTOCOL_INFO} + {$IFDEF UNICODE} + SO_PROTOCOL_INFO = SO_PROTOCOL_INFOW; + {$ELSE} + SO_PROTOCOL_INFO = SO_PROTOCOL_INFOA; + {$ENDIF} + {$EXTERNALSYM PVD_CONFIG} + PVD_CONFIG = $3001; // configuration info for service provider + {$EXTERNALSYM SO_CONDITIONAL_ACCEPT} + SO_CONDITIONAL_ACCEPT = $3002; // enable true conditional accept: + // connection is not ack-ed to the + // other side until conditional + // function returns CF_ACCEPT + {$IFNDEF WINCE} + {$EXTERNALSYM SO_RANDOMIZE_PORT} + SO_RANDOMIZE_PORT = $3005; // randomize assignment of wildcard ports + {$EXTERNALSYM SO_PORT_SCALABILITY} + SO_PORT_SCALABILITY = $3006; // enable port scalability + {$ENDIF} +// Address families. + {$EXTERNALSYM AF_UNSPEC} + AF_UNSPEC = 0; // unspecified + {$EXTERNALSYM AF_UNIX} + AF_UNIX = 1; // local to host (pipes, portals) + {$EXTERNALSYM AF_INET} + AF_INET = 2; // internetwork: UDP, TCP, etc. + {$EXTERNALSYM AF_IMPLINK} + AF_IMPLINK = 3; // arpanet imp addresses + {$EXTERNALSYM AF_PUP} + AF_PUP = 4; // pup protocols: e.g. BSP + {$EXTERNALSYM AF_CHAOS} + AF_CHAOS = 5; // mit CHAOS protocols + {$EXTERNALSYM AF_IPX} + AF_IPX = 6; // ipx and SPX + {$EXTERNALSYM AF_NS} + AF_NS = AF_IPX; // xerOX NS protocols + {$EXTERNALSYM AF_ISO} + AF_ISO = 7; // iso protocols + {$EXTERNALSYM AF_OSI} + AF_OSI = AF_ISO; // osi is ISO + {$EXTERNALSYM AF_ECMA} + AF_ECMA = 8; // european computer manufacturers + {$EXTERNALSYM AF_DATAKIT} + AF_DATAKIT = 9; // datakit protocols + {$EXTERNALSYM AF_CCITT} + AF_CCITT = 10; // cciTT protocols, X.25 etc + {$EXTERNALSYM AF_SNA} + AF_SNA = 11; // ibm SNA + {$EXTERNALSYM AF_DECNET} + AF_DECNET = 12; // decnet + {$EXTERNALSYM AF_DLI} + AF_DLI = 13; // direct data link interface + {$EXTERNALSYM AF_LAT} + AF_LAT = 14; // lat + {$EXTERNALSYM AF_HYLINK} + AF_HYLINK = 15; // nsc Hyperchannel + {$EXTERNALSYM AF_APPLETALK} + AF_APPLETALK = 16; // appleTalk + {$EXTERNALSYM AF_NETBIOS} + AF_NETBIOS = 17; // netBios-style addresses + {$EXTERNALSYM AF_VOICEVIEW} + AF_VOICEVIEW = 18; // voiceView + {$EXTERNALSYM AF_FIREFOX} + AF_FIREFOX = 19; // fireFox + {$EXTERNALSYM AF_UNKNOWN1} + AF_UNKNOWN1 = 20; // somebody is using this! + {$EXTERNALSYM AF_BAN} + AF_BAN = 21; // banyan + {$IFDEF WINCE} + {$EXTERNALSYM AF_IRDA} + AF_IRDA = 22; //* IrDA */ + {$ELSE} + {$EXTERNALSYM AF_ATM} + AF_ATM = 22; // native ATM Services + {$ENDIF} + {$EXTERNALSYM AF_INET6} + AF_INET6 = 23; // internetwork Version 6 + {$EXTERNALSYM AF_CLUSTER} + AF_CLUSTER = 24; // microsoft Wolfpack + {$EXTERNALSYM AF_12844} + AF_12844 = 25; // ieeE 1284.4 WG AF + {$IFDEF WINCE} + {$EXTERNALSYM AF_ATM} + AF_ATM = 26; //* Native ATM Services */ + {$ELSE} + {$EXTERNALSYM AF_IRDA} + AF_IRDA = 26; // irdA + {$ENDIF} + {$EXTERNALSYM AF_NETDES} + AF_NETDES = 28; // network Designers OSI & gateway enabled protocols + {$EXTERNALSYM AF_TCNPROCESS} + AF_TCNPROCESS = 29; + {$EXTERNALSYM AF_TCNMESSAGE} + AF_TCNMESSAGE = 30; + {$EXTERNALSYM AF_ICLFXBM} + AF_ICLFXBM = 31; + + {$EXTERNALSYM AF_MAX} + AF_MAX = 32; + +// protocol families, same as address families for now. + + {$EXTERNALSYM PF_UNSPEC} + PF_UNSPEC = AF_UNSPEC; + {$EXTERNALSYM PF_UNIX} + PF_UNIX = AF_UNIX; + {$EXTERNALSYM PF_INET} + PF_INET = AF_INET; + {$EXTERNALSYM PF_IMPLINK} + PF_IMPLINK = AF_IMPLINK; + {$EXTERNALSYM PF_PUP} + PF_PUP = AF_PUP; + {$EXTERNALSYM PF_CHAOS} + PF_CHAOS = AF_CHAOS; + {$EXTERNALSYM PF_NS} + PF_NS = AF_NS; + {$EXTERNALSYM PF_IPX} + PF_IPX = AF_IPX; + {$EXTERNALSYM PF_ISO} + PF_ISO = AF_ISO; + {$EXTERNALSYM PF_OSI} + PF_OSI = AF_OSI; + {$EXTERNALSYM PF_ECMA} + PF_ECMA = AF_ECMA; + {$EXTERNALSYM PF_DATAKIT} + PF_DATAKIT = AF_DATAKIT; + {$EXTERNALSYM PF_CCITT} + PF_CCITT = AF_CCITT; + {$EXTERNALSYM PF_SNA} + PF_SNA = AF_SNA; + {$EXTERNALSYM PF_DECNET} + PF_DECNET = AF_DECNET; + {$EXTERNALSYM PF_DLI} + PF_DLI = AF_DLI; + {$EXTERNALSYM PF_LAT} + PF_LAT = AF_LAT; + {$EXTERNALSYM PF_HYLINK} + PF_HYLINK = AF_HYLINK; + {$EXTERNALSYM PF_APPLETALK} + PF_APPLETALK = AF_APPLETALK; + {$EXTERNALSYM PF_VOICEVIEW} + PF_VOICEVIEW = AF_VOICEVIEW; + {$EXTERNALSYM PF_FIREFOX} + PF_FIREFOX = AF_FIREFOX; + {$EXTERNALSYM PF_UNKNOWN1} + PF_UNKNOWN1 = AF_UNKNOWN1; + {$EXTERNALSYM pf_ban} + PF_BAN = AF_BAN; + {$EXTERNALSYM PF_ATM} + PF_ATM = AF_ATM; + {$EXTERNALSYM PF_INET6} + PF_INET6 = AF_INET6; + + {$EXTERNALSYM PF_MAX} + PF_MAX = AF_MAX; + + {$EXTERNALSYM _SS_MAXSIZE} + _SS_MAXSIZE = 128; + {$EXTERNALSYM _SS_ALIGNSIZE} + _SS_ALIGNSIZE = SizeOf(Int64); + {$EXTERNALSYM _SS_PAD1SIZE} + _SS_PAD1SIZE = _SS_ALIGNSIZE - SizeOf(short); + {$EXTERNALSYM _SS_PAD2SIZE} + _SS_PAD2SIZE = _SS_MAXSIZE - (SizeOf(short) + _SS_PAD1SIZE + _SS_ALIGNSIZE); + +type + {$NODEFINE SunB} + SunB = record + s_b1, s_b2, s_b3, s_b4: u_char; + end; + + {$NODEFINE SunW} + SunW = record + s_w1, s_w2: u_short; + end; + + {$EXTERNALSYM in_addr} + in_addr = record + case integer of + 0: (S_un_b: SunB); + 1: (S_un_w: SunW); + 2: (S_addr: u_long); + end; + {$NODEFINE TInAddr} + TInAddr = in_addr; + {$NODEFINE PInAddr} + PInAddr = ^TInAddr; + + // Structure used by kernel to store most addresses. + + {$EXTERNALSYM sockaddr_in} + sockaddr_in = record + case Integer of + 0: (sin_family : u_short; + sin_port : u_short; + sin_addr : TInAddr; + sin_zero : array[0..7] of AnsiChar); + 1: (sa_family : u_short; + sa_data : array[0..13] of AnsiChar) + end; + {$NODEFINE TSockAddrIn} + TSockAddrIn = sockaddr_in; + {$NODEFINE PSockAddrIn} + PSockAddrIn = ^TSockAddrIn; + + {$NODEFINE TSockAddr} + TSockAddr = TSockAddrIn; + {$EXTERNALSYM SOCKADDR} + SOCKADDR = TSockAddr; + {$EXTERNALSYM PSOCKADDR} + PSOCKADDR = ^TSockAddr; + {$EXTERNALSYM LPSOCKADDR} + LPSOCKADDR = PSOCKADDR; + + {$EXTERNALSYM SOCKADDR_STORAGE} + SOCKADDR_STORAGE = record + ss_family: short; // Address family. + __ss_pad1: array[0.._SS_PAD1SIZE-1] of AnsiChar; // 6 byte pad, this is to make + // implementation specific pad up to + // alignment field that follows explicit + // in the data structure. + __ss_align: Int64; // Field to force desired structure. + __ss_pad2: array[0.._SS_PAD2SIZE-1] of AnsiChar; // 112 byte pad to achieve desired size; + // _SS_MAXSIZE value minus size of + // ss_family, __ss_pad1, and + // __ss_align fields is 112. + end; + {$NODEFINE TSockAddrStorage} + TSockAddrStorage = SOCKADDR_STORAGE; + {$NODEFINE PSockAddrStorage} + PSockAddrStorage = ^TSockAddrStorage; + {$EXTERNALSYM PSOCKADDR_STORAGE} + PSOCKADDR_STORAGE = PSockAddrStorage; + {$EXTERNALSYM LPSOCKADDR_STORAGE} + LPSOCKADDR_STORAGE = PSOCKADDR_STORAGE; + + // Structure used by kernel to pass protocol information in raw sockets. + {$EXTERNALSYM sockproto} + sockproto = record + sp_family : u_short; + sp_protocol : u_short; + end; + {$NODEFINE TSockProto} + TSockProto = sockproto; + {$NODEFINE PSockProto} + PSockProto = ^TSockProto; + +// Structure used for manipulating linger option. + {$EXTERNALSYM linger} + linger = record + l_onoff: u_short; + l_linger: u_short; + end; + {$NODEFINE TLinger} + TLinger = linger; + {$EXTERNALSYM PLINGER} + PLINGER = ^TLinger; + {$EXTERNALSYM LPLINGER} + LPLINGER = PLINGER; + +const + {$EXTERNALSYM INADDR_ANY} + INADDR_ANY = $00000000; + {$EXTERNALSYM INADDR_LOOPBACK} + INADDR_LOOPBACK = $7F000001; + {$EXTERNALSYM INADDR_BROADCAST} + INADDR_BROADCAST = $FFFFFFFF; + {$EXTERNALSYM INADDR_NONE} + INADDR_NONE = $FFFFFFFF; + + {$EXTERNALSYM ADDR_ANY} + ADDR_ANY = INADDR_ANY; + + {$EXTERNALSYM SOL_SOCKET} + SOL_SOCKET = $FFFF; // options for socket level + + {$EXTERNALSYM MSG_OOB} + MSG_OOB = $1; // process out-of-band data + {$EXTERNALSYM MSG_PEEK} + MSG_PEEK = $2; // peek at incoming message + {$EXTERNALSYM MSG_DONTROUTE} + MSG_DONTROUTE = $4; // send without using routing tables + + {$EXTERNALSYM MSG_PARTIAL} + MSG_PARTIAL = $8000; // partial send or recv for message xport + +// WinSock 2 extension -- new flags for WSASend(), WSASendTo(), WSARecv() and WSARecvFrom() + {$EXTERNALSYM MSG_INTERRUPT} + MSG_INTERRUPT = $10; // send/recv in the interrupt context + {$EXTERNALSYM MSG_MAXIOVLEN} + MSG_MAXIOVLEN = 16; + +// Define constant based on rfc883, used by gethostbyxxxx() calls. + + {$EXTERNALSYM MAXGETHOSTSTRUCT} + MAXGETHOSTSTRUCT = 1024; + +// Maximum queue length specifiable by listen. + {$EXTERNALSYM SOMAXCONN} + SOMAXCONN = $7FFFFFFF; + +// WinSock 2 extension -- bit values and indices for FD_XXX network events + {$EXTERNALSYM FD_READ_BIT} + FD_READ_BIT = 0; + {$EXTERNALSYM FD_WRITE_BIT} + FD_WRITE_BIT = 1; + {$EXTERNALSYM FD_OOB_BIT} + FD_OOB_BIT = 2; + {$EXTERNALSYM FD_ACCEPT_BIT} + FD_ACCEPT_BIT = 3; + {$EXTERNALSYM FD_CONNECT_BIT} + FD_CONNECT_BIT = 4; + {$EXTERNALSYM FD_CLOSE_BIT} + FD_CLOSE_BIT = 5; + {$EXTERNALSYM fd_qos_bit} + FD_QOS_BIT = 6; + {$EXTERNALSYM FD_GROUP_QOS_BIT} + FD_GROUP_QOS_BIT = 7; + {$EXTERNALSYM FD_ROUTING_INTERFACE_CHANGE_BIT} + FD_ROUTING_INTERFACE_CHANGE_BIT = 8; + {$EXTERNALSYM FD_ADDRESS_LIST_CHANGE_BIT} + FD_ADDRESS_LIST_CHANGE_BIT = 9; + + {$EXTERNALSYM FD_MAX_EVENTS} + FD_MAX_EVENTS = 10; + + {$EXTERNALSYM FD_READ} + FD_READ = (1 shl FD_READ_BIT); + {$EXTERNALSYM FD_WRITE} + FD_WRITE = (1 shl FD_WRITE_BIT); + {$EXTERNALSYM FD_OOB} + FD_OOB = (1 shl FD_OOB_BIT); + {$EXTERNALSYM FD_ACCEPT} + FD_ACCEPT = (1 shl FD_ACCEPT_BIT); + {$EXTERNALSYM FD_CONNECT} + FD_CONNECT = (1 shl FD_CONNECT_BIT); + {$EXTERNALSYM FD_CLOSE} + FD_CLOSE = (1 shl FD_CLOSE_BIT); + {$EXTERNALSYM FD_QOS} + FD_QOS = (1 shl FD_QOS_BIT); + {$EXTERNALSYM FD_GROUP_QOS} + FD_GROUP_QOS = (1 shl FD_GROUP_QOS_BIT); + {$EXTERNALSYM FD_ROUTING_INTERFACE_CHANGE} + FD_ROUTING_INTERFACE_CHANGE = (1 shl FD_ROUTING_INTERFACE_CHANGE_BIT); + {$EXTERNALSYM FD_ADDRESS_LIST_CHANGE} + FD_ADDRESS_LIST_CHANGE = (1 shl FD_ADDRESS_LIST_CHANGE_BIT); + + {$EXTERNALSYM FD_ALL_EVENTS} + FD_ALL_EVENTS = (1 shl FD_MAX_EVENTS) - 1; + +// All Winapi.Windows Sockets error constants are biased by WSABASEERR from the "normal" + + {$EXTERNALSYM WSABASEERR} + WSABASEERR = 10000; + +// Winapi.Windows Sockets definitions of regular Microsoft C error constants + + {$EXTERNALSYM WSAEINTR} + WSAEINTR = WSABASEERR+ 4; + {$EXTERNALSYM WSAEBADF} + WSAEBADF = WSABASEERR+ 9; + {$EXTERNALSYM WSAEACCES} + WSAEACCES = WSABASEERR+ 13; + {$EXTERNALSYM WSAEFAULT} + WSAEFAULT = WSABASEERR+ 14; + {$EXTERNALSYM WSAEINVAL} + WSAEINVAL = WSABASEERR+ 22; + {$EXTERNALSYM WSAEMFILE} + WSAEMFILE = WSABASEERR+ 24; + +// Winapi.Windows Sockets definitions of regular Berkeley error constants + + {$EXTERNALSYM WSAEWOULDBLOCK} + WSAEWOULDBLOCK = WSABASEERR+ 35; + {$EXTERNALSYM WSAEINPROGRESS} + WSAEINPROGRESS = WSABASEERR+ 36; + {$EXTERNALSYM WSAEALREADY} + WSAEALREADY = WSABASEERR+ 37; + {$EXTERNALSYM WSAENOTSOCK} + WSAENOTSOCK = WSABASEERR+ 38; + {$EXTERNALSYM WSAEDESTADDRREQ} + WSAEDESTADDRREQ = WSABASEERR+ 39; + {$EXTERNALSYM WSAEMSGSIZE} + WSAEMSGSIZE = WSABASEERR+ 40; + {$EXTERNALSYM WSAEPROTOTYPE} + WSAEPROTOTYPE = WSABASEERR+ 41; + {$EXTERNALSYM WSAENOPROTOOPT} + WSAENOPROTOOPT = WSABASEERR+ 42; + {$EXTERNALSYM WSAEPROTONOSUPPORT} + WSAEPROTONOSUPPORT = WSABASEERR+ 43; + {$EXTERNALSYM WSAESOCKTNOSUPPORT} + WSAESOCKTNOSUPPORT = WSABASEERR+ 44; + {$EXTERNALSYM WSAEOPNOTSUPP} + WSAEOPNOTSUPP = WSABASEERR+ 45; + {$EXTERNALSYM WSAEPFNOSUPPORT} + WSAEPFNOSUPPORT = WSABASEERR+ 46; + {$EXTERNALSYM WSAEAFNOSUPPORT} + WSAEAFNOSUPPORT = WSABASEERR+ 47; + {$EXTERNALSYM WSAEADDRINUSE} + WSAEADDRINUSE = WSABASEERR+ 48; + {$EXTERNALSYM WSAEADDRNOTAVAIL} + WSAEADDRNOTAVAIL = WSABASEERR+ 49; + {$EXTERNALSYM WSAENETDOWN} + WSAENETDOWN = WSABASEERR+ 50; + {$EXTERNALSYM WSAENETUNREACH} + WSAENETUNREACH = WSABASEERR+ 51; + {$EXTERNALSYM WSAENETRESET} + WSAENETRESET = WSABASEERR+ 52; + {$EXTERNALSYM WSAECONNABORTED} + WSAECONNABORTED = WSABASEERR+ 53; + {$EXTERNALSYM WSAECONNRESET} + WSAECONNRESET = WSABASEERR+ 54; + {$EXTERNALSYM WSAENOBUFS} + WSAENOBUFS = WSABASEERR+ 55; + {$EXTERNALSYM WSAEISCONN} + WSAEISCONN = WSABASEERR+ 56; + {$EXTERNALSYM WSAENOTCONN} + WSAENOTCONN = WSABASEERR+ 57; + {$EXTERNALSYM WSAESHUTDOWN} + WSAESHUTDOWN = WSABASEERR+ 58; + {$EXTERNALSYM WSAETOOMANYREFS} + WSAETOOMANYREFS = WSABASEERR+ 59; + {$EXTERNALSYM WSAETIMEDOUT} + WSAETIMEDOUT = WSABASEERR+ 60; + {$EXTERNALSYM WSAECONNREFUSED} + WSAECONNREFUSED = WSABASEERR+ 61; + {$EXTERNALSYM WSAELOOP} + WSAELOOP = WSABASEERR+ 62; + {$EXTERNALSYM WSAENAMETOOLONG} + WSAENAMETOOLONG = WSABASEERR+ 63; + {$EXTERNALSYM WSAEHOSTDOWN} + WSAEHOSTDOWN = WSABASEERR+ 64; + {$EXTERNALSYM WSAEHOSTUNREACH} + WSAEHOSTUNREACH = WSABASEERR+ 65; + {$EXTERNALSYM wsaenotempty} + WSAENOTEMPTY = WSABASEERR+ 66; + {$EXTERNALSYM WSAEPROCLIM} + WSAEPROCLIM = WSABASEERR+ 67; + {$EXTERNALSYM WSAEUSERS} + WSAEUSERS = WSABASEERR+ 68; + {$EXTERNALSYM WSAEDQUOT} + WSAEDQUOT = WSABASEERR+ 69; + {$EXTERNALSYM WSAESTALE} + WSAESTALE = WSABASEERR+ 70; + {$EXTERNALSYM WSAEREMOTE} + WSAEREMOTE = WSABASEERR+ 71; + +// Extended Winapi.Windows Sockets error constant definitions + + {$EXTERNALSYM WSASYSNOTREADY} + WSASYSNOTREADY = WSABASEERR+ 91; + {$EXTERNALSYM WSAVERNOTSUPPORTED} + WSAVERNOTSUPPORTED = WSABASEERR+ 92; + {$EXTERNALSYM WSANOTINITIALISED} + WSANOTINITIALISED = WSABASEERR+ 93; + {$EXTERNALSYM WSAEDISCON} + WSAEDISCON = WSABASEERR+101; + {$EXTERNALSYM WSAENOMORE} + WSAENOMORE = WSABASEERR+102; + {$EXTERNALSYM WSAECANCELLED} + WSAECANCELLED = WSABASEERR+103; + {$EXTERNALSYM WSAEINVALIDPROCTABLE} + WSAEINVALIDPROCTABLE = WSABASEERR+104; + {$EXTERNALSYM WSAEINVALIDPROVIDER} + WSAEINVALIDPROVIDER = WSABASEERR+105; + {$EXTERNALSYM WSAEPROVIDERFAILEDINIT} + WSAEPROVIDERFAILEDINIT = WSABASEERR+106; + {$EXTERNALSYM WSASYSCALLFAILURE} + WSASYSCALLFAILURE = WSABASEERR+107; + {$EXTERNALSYM WSASERVICE_NOT_FOUND} + WSASERVICE_NOT_FOUND = WSABASEERR+108; + {$EXTERNALSYM WSATYPE_NOT_FOUND} + WSATYPE_NOT_FOUND = WSABASEERR+109; + {$EXTERNALSYM WSA_E_NO_MORE} + WSA_E_NO_MORE = WSABASEERR+110; + {$EXTERNALSYM WSA_E_CANCELLED} + WSA_E_CANCELLED = WSABASEERR+111; + {$EXTERNALSYM WSAEREFUSED} + WSAEREFUSED = WSABASEERR+112; + + {$IFDEF WINCE} + WSAEDUPLICATE_NAME = WSABASEERR+900; + {$ENDIF} + +{ Error return codes from gethostbyname() and gethostbyaddr() + (when using the resolver). Note that these errors are + retrieved via WSAGetLastError() and must therefore follow + the rules for avoiding clashes with error numbers from + specific implementations or language run-time systems. + For this reason the codes are based at WSABASEERR+1001. + Note also that [WSA]NO_ADDRESS is defined only for + compatibility purposes. } + +// Authoritative Answer: Host not found + {$EXTERNALSYM WSAHOST_NOT_FOUND} + WSAHOST_NOT_FOUND = WSABASEERR+1001; + {$EXTERNALSYM HOST_NOT_FOUND} + HOST_NOT_FOUND = WSAHOST_NOT_FOUND; + +// Non-Authoritative: Host not found, or SERVERFAIL + {$EXTERNALSYM WSATRY_AGAIN} + WSATRY_AGAIN = WSABASEERR+1002; + {$EXTERNALSYM TRY_AGAIN} + TRY_AGAIN = WSATRY_AGAIN; + +// Non recoverable errors, FORMERR, REFUSED, NOTIMP + {$EXTERNALSYM WSANO_RECOVERY} + WSANO_RECOVERY = WSABASEERR+1003; + {$EXTERNALSYM NO_RECOVERY} + NO_RECOVERY = WSANO_RECOVERY; + +// Valid name, no data record of requested type + {$EXTERNALSYM WSANO_DATA} + WSANO_DATA = WSABASEERR+1004; + {$EXTERNALSYM NO_DATA} + NO_DATA = WSANO_DATA; + +// no address, look for MX record + {$EXTERNALSYM WSANO_ADDRESS} + WSANO_ADDRESS = WSANO_DATA; + {$EXTERNALSYM NO_ADDRESS} + NO_ADDRESS = WSANO_ADDRESS; + +// Define QOS related error return codes + + {$EXTERNALSYM WSA_QOS_RECEIVERS} + WSA_QOS_RECEIVERS = WSABASEERR+1005; // at least one reserve has arrived + {$EXTERNALSYM WSA_QOS_SENDERS} + WSA_QOS_SENDERS = WSABASEERR+1006; // at least one path has arrived + {$EXTERNALSYM WSA_QOS_NO_SENDERS} + WSA_QOS_NO_SENDERS = WSABASEERR+1007; // there are no senders + {$EXTERNALSYM WSA_QOS_NO_RECEIVERS} + WSA_QOS_NO_RECEIVERS = WSABASEERR+1008; // there are no receivers + {$EXTERNALSYM WSA_QOS_REQUEST_CONFIRMED} + WSA_QOS_REQUEST_CONFIRMED = WSABASEERR+1009; // reserve has been confirmed + {$EXTERNALSYM WSA_QOS_ADMISSION_FAILURE} + WSA_QOS_ADMISSION_FAILURE = WSABASEERR+1010; // error due to lack of resources + {$EXTERNALSYM WSA_QOS_POLICY_FAILURE} + WSA_QOS_POLICY_FAILURE = WSABASEERR+1011; // rejected for administrative reasons - bad credentials + {$EXTERNALSYM WSA_QOS_BAD_STYLE} + WSA_QOS_BAD_STYLE = WSABASEERR+1012; // unknown or conflicting style + {$EXTERNALSYM WSA_QOS_BAD_OBJECT} + WSA_QOS_BAD_OBJECT = WSABASEERR+1013; // problem with some part of the filterspec or providerspecific buffer in general + {$EXTERNALSYM WSA_QOS_TRAFFIC_CTRL_ERROR} + WSA_QOS_TRAFFIC_CTRL_ERROR = WSABASEERR+1014; // problem with some part of the flowspec + {$EXTERNALSYM WSA_QOS_GENERIC_ERROR} + WSA_QOS_GENERIC_ERROR = WSABASEERR+1015; // general error + {$EXTERNALSYM WSA_QOS_ESERVICETYPE} + WSA_QOS_ESERVICETYPE = WSABASEERR+1016; // invalid service type in flowspec + {$EXTERNALSYM WSA_QOS_EFLOWSPEC} + WSA_QOS_EFLOWSPEC = WSABASEERR+1017; // invalid flowspec + {$EXTERNALSYM WSA_QOS_EPROVSPECBUF} + WSA_QOS_EPROVSPECBUF = WSABASEERR+1018; // invalid provider specific buffer + {$EXTERNALSYM WSA_QOS_EFILTERSTYLE} + WSA_QOS_EFILTERSTYLE = WSABASEERR+1019; // invalid filter style + {$EXTERNALSYM WSA_QOS_EFILTERTYPE} + WSA_QOS_EFILTERTYPE = WSABASEERR+1020; // invalid filter type + {$EXTERNALSYM WSA_QOS_EFILTERCOUNT} + WSA_QOS_EFILTERCOUNT = WSABASEERR+1021; // incorrect number of filters + {$EXTERNALSYM WSA_QOS_EOBJLENGTH} + WSA_QOS_EOBJLENGTH = WSABASEERR+1022; // invalid object length + {$EXTERNALSYM WSA_QOS_EFLOWCOUNT} + WSA_QOS_EFLOWCOUNT = WSABASEERR+1023; // incorrect number of flows + {$EXTERNALSYM WSA_QOS_EUNKOWNPSOBJ} + WSA_QOS_EUNKOWNPSOBJ = WSABASEERR+1024; // unknown object in provider specific buffer + {$EXTERNALSYM WSA_QOS_EPOLICYOBJ} + WSA_QOS_EPOLICYOBJ = WSABASEERR+1025; // invalid policy object in provider specific buffer + {$EXTERNALSYM WSA_QOS_EFLOWDESC} + WSA_QOS_EFLOWDESC = WSABASEERR+1026; // invalid flow descriptor in the list + {$EXTERNALSYM WSA_QOS_EPSFLOWSPEC} + WSA_QOS_EPSFLOWSPEC = WSABASEERR+1027; // inconsistent flow spec in provider specific buffer + {$EXTERNALSYM WSA_QOS_EPSFILTERSPEC} + WSA_QOS_EPSFILTERSPEC = WSABASEERR+1028; // invalid filter spec in provider specific buffer + {$EXTERNALSYM WSA_QOS_ESDMODEOBJ} + WSA_QOS_ESDMODEOBJ = WSABASEERR+1029; // invalid shape discard mode object in provider specific buffer + {$EXTERNALSYM WSA_QOS_ESHAPERATEOBJ} + WSA_QOS_ESHAPERATEOBJ = WSABASEERR+1030; // invalid shaping rate object in provider specific buffer + {$EXTERNALSYM WSA_QOS_RESERVED_PETYPE} + WSA_QOS_RESERVED_PETYPE = WSABASEERR+1031; // reserved policy element in provider specific buffer + + +{ WinSock 2 extension -- new error codes and type definition } + {$EXTERNALSYM WSA_IO_PENDING} + WSA_IO_PENDING = ERROR_IO_PENDING; + {$EXTERNALSYM WSA_IO_INCOMPLETE} + WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE; + {$EXTERNALSYM WSA_INVALID_HANDLE} + WSA_INVALID_HANDLE = ERROR_INVALID_HANDLE; + {$EXTERNALSYM WSA_INVALID_PARAMETER} + WSA_INVALID_PARAMETER = ERROR_INVALID_PARAMETER; + {$EXTERNALSYM WSA_NOT_ENOUGH_MEMORY} + WSA_NOT_ENOUGH_MEMORY = ERROR_NOT_ENOUGH_MEMORY; + {$EXTERNALSYM WSA_OPERATION_ABORTED} + WSA_OPERATION_ABORTED = ERROR_OPERATION_ABORTED; + {$EXTERNALSYM WSA_INVALID_EVENT} + WSA_INVALID_EVENT = WSAEVENT(nil); + {$EXTERNALSYM WSA_MAXIMUM_WAIT_EVENTS} + WSA_MAXIMUM_WAIT_EVENTS = MAXIMUM_WAIT_OBJECTS; + {$EXTERNALSYM WSA_WAIT_FAILED} + WSA_WAIT_FAILED = $FFFFFFFF; + {$EXTERNALSYM WSA_WAIT_EVENT_0} + WSA_WAIT_EVENT_0 = WAIT_OBJECT_0; + {$EXTERNALSYM WSA_WAIT_IO_COMPLETION} + WSA_WAIT_IO_COMPLETION = WAIT_IO_COMPLETION; + {$EXTERNALSYM WSA_WAIT_TIMEOUT} + WSA_WAIT_TIMEOUT = WAIT_TIMEOUT; + {$EXTERNALSYM WSA_INFINITE} + WSA_INFINITE = INFINITE; + +{ Winapi.Windows Sockets errors redefined as regular Berkeley error constants. + These are commented out in Winapi.Windows NT to avoid conflicts with errno.h. + Use the WSA constants instead. } + + {$EXTERNALSYM EWOULDBLOCK} + EWOULDBLOCK = WSAEWOULDBLOCK; + {$EXTERNALSYM EINPROGRESS} + EINPROGRESS = WSAEINPROGRESS; + {$EXTERNALSYM EALREADY} + EALREADY = WSAEALREADY; + {$EXTERNALSYM ENOTSOCK} + ENOTSOCK = WSAENOTSOCK; + {$EXTERNALSYM EDESTADDRREQ} + EDESTADDRREQ = WSAEDESTADDRREQ; + {$EXTERNALSYM EMSGSIZE} + EMSGSIZE = WSAEMSGSIZE; + {$EXTERNALSYM EPROTOTYPE} + EPROTOTYPE = WSAEPROTOTYPE; + {$EXTERNALSYM ENOPROTOOPT} + ENOPROTOOPT = WSAENOPROTOOPT; + {$EXTERNALSYM EPROTONOSUPPORT} + EPROTONOSUPPORT = WSAEPROTONOSUPPORT; + {$EXTERNALSYM ESOCKTNOSUPPORT} + ESOCKTNOSUPPORT = WSAESOCKTNOSUPPORT; + {$EXTERNALSYM EOPNOTSUPP} + EOPNOTSUPP = WSAEOPNOTSUPP; + {$EXTERNALSYM EPFNOSUPPORT} + EPFNOSUPPORT = WSAEPFNOSUPPORT; + {$EXTERNALSYM EAFNOSUPPORT} + EAFNOSUPPORT = WSAEAFNOSUPPORT; + {$EXTERNALSYM EADDRINUSE} + EADDRINUSE = WSAEADDRINUSE; + {$EXTERNALSYM EADDRNOTAVAIL} + EADDRNOTAVAIL = WSAEADDRNOTAVAIL; + {$EXTERNALSYM ENETDOWN} + ENETDOWN = WSAENETDOWN; + {$EXTERNALSYM ENETUNREACH} + ENETUNREACH = WSAENETUNREACH; + {$EXTERNALSYM ENETRESET} + ENETRESET = WSAENETRESET; + {$EXTERNALSYM ECONNABORTED} + ECONNABORTED = WSAECONNABORTED; + {$EXTERNALSYM ECONNRESET} + ECONNRESET = WSAECONNRESET; + {$EXTERNALSYM ENOBUFS} + ENOBUFS = WSAENOBUFS; + {$EXTERNALSYM EISCONN} + EISCONN = WSAEISCONN; + {$EXTERNALSYM ENOTCONN} + ENOTCONN = WSAENOTCONN; + {$EXTERNALSYM ESHUTDOWN} + ESHUTDOWN = WSAESHUTDOWN; + {$EXTERNALSYM ETOOMANYREFS} + ETOOMANYREFS = WSAETOOMANYREFS; + {$EXTERNALSYM ETIMEDOUT} + ETIMEDOUT = WSAETIMEDOUT; + {$EXTERNALSYM ECONNREFUSED} + ECONNREFUSED = WSAECONNREFUSED; + {$EXTERNALSYM ELOOP} + ELOOP = WSAELOOP; + {$EXTERNALSYM ENAMETOOLONG} + ENAMETOOLONG = WSAENAMETOOLONG; + {$EXTERNALSYM EHOSTDOWN} + EHOSTDOWN = WSAEHOSTDOWN; + {$EXTERNALSYM EHOSTUNREACH} + EHOSTUNREACH = WSAEHOSTUNREACH; + {$EXTERNALSYM ENOTEMPTY} + ENOTEMPTY = WSAENOTEMPTY; + {$EXTERNALSYM EPROCLIM} + EPROCLIM = WSAEPROCLIM; + {$EXTERNALSYM EUSERS} + EUSERS = WSAEUSERS; + {$EXTERNALSYM EDQUOT} + EDQUOT = WSAEDQUOT; + {$EXTERNALSYM ESTALE} + ESTALE = WSAESTALE; + {$EXTERNALSYM EREMOTE} + EREMOTE = WSAEREMOTE; + + {$EXTERNALSYM WSADESCRIPTION_LEN} + WSADESCRIPTION_LEN = 256; + {$EXTERNALSYM WSASYS_STATUS_LEN} + WSASYS_STATUS_LEN = 128; + +type + {$EXTERNALSYM WSADATA} + WSADATA = record + wVersion : Word; + wHighVersion : Word; + {$IFDEF _WIN64} + iMaxSockets : Word; + iMaxUdpDg : Word; + lpVendorInfo : PAnsiChar; + szDescription : array[0..WSADESCRIPTION_LEN] of AnsiChar; + szSystemStatus : array[0..WSASYS_STATUS_LEN] of AnsiChar; + {$ELSE} + szDescription : array[0..WSADESCRIPTION_LEN] of AnsiChar; + szSystemStatus : array[0..WSASYS_STATUS_LEN] of AnsiChar; + iMaxSockets : Word; + iMaxUdpDg : Word; + lpVendorInfo : PAnsiChar; + {$ENDIF} + end; + {$NODEFINE TWSAData} + TWSAData = WSADATA; + {$NODEFINE PWSAData} + PWSAData = ^TWSAData; + {$EXTERNALSYM LPWSADATA} + LPWSADATA = PWSAData; + + {$EXTERNALSYM WSAOVERLAPPED} + WSAOVERLAPPED = TOverlapped; + {$NODEFINE TWSAOverlapped} + TWSAOverlapped = WSAOVERLAPPED; + {$NODEFINE PWSAOverlapped} + PWSAOverlapped = ^TWSAOverlapped; + {$EXTERNALSYM LPWSAOVERLAPPED} + LPWSAOVERLAPPED = PWSAOverlapped; + {$IFNDEF WINCE} + {$EXTERNALSYM WSC_PROVIDER_INFO_TYPE} + {$EXTERNALSYM PROVIDERINFOLSPCATEGORIES} + {$EXTERNALSYM PROVIDERINFOAUDIT} + WSC_PROVIDER_INFO_TYPE = (ProviderInfoLspCategories, ProviderInfoAudit); + {$ENDIF} + +{ WinSock 2 extension -- WSABUF and QOS struct, include qos.h } +{ to pull in FLOWSPEC and related definitions } + + {$EXTERNALSYM WSABUF} + WSABUF = record + len: u_long; { the length of the buffer } + buf: PAnsiChar; { the pointer to the buffer } + end; + {$NODEFINE TWSABuf} + TWSABuf = WSABUF; + {$NODEFINE PWSABuf} + PWSABuf = ^TWSABuf; + {$EXTERNALSYM LPWSABUF} + LPWSABUF = PWSABUF; + + {$EXTERNALSYM SERVICETYPE} + SERVICETYPE = LongInt; + {$NODEFINE TServiceType} + TServiceType = SERVICETYPE; + + {$EXTERNALSYM FLOWSPEC} + FLOWSPEC = record + TokenRate, // In Bytes/sec + TokenBucketSize, // In Bytes + PeakBandwidth, // In Bytes/sec + Latency, // In microseconds + DelayVariation : LongInt;// In microseconds + ServiceType : TServiceType; + MaxSduSize, MinimumPolicedSize : LongInt;// In Bytes + end; + {$NODEFINE TFlowSpec} + TFlowSpec = FLOWSPEC; + {$EXTERNALSYM PFLOWSPEC} + PFLOWSPEC = ^TFlowSpec; + {$EXTERNALSYM LPFLOWSPEC} + LPFLOWSPEC = PFLOWSPEC; + + {$EXTERNALSYM QOS} + QOS = record + SendingFlowspec: TFlowSpec; { the flow spec for data sending } + ReceivingFlowspec: TFlowSpec; { the flow spec for data receiving } + ProviderSpecific: TWSABuf; { additional provider specific stuff } + end; + {$NODEFINE TQualityOfService} + TQualityOfService = QOS; + {$NODEFINE PQOS} + PQOS = ^QOS; + {$EXTERNALSYM LPQOS} + LPQOS = PQOS; + +const + {$EXTERNALSYM SERVICETYPE_NOTRAFFIC} + SERVICETYPE_NOTRAFFIC = $00000000; // No data in this direction + {$EXTERNALSYM SERVICETYPE_BESTEFFORT} + SERVICETYPE_BESTEFFORT = $00000001; // Best Effort + {$EXTERNALSYM SERVICETYPE_CONTROLLEDLOAD} + SERVICETYPE_CONTROLLEDLOAD = $00000002; // Controlled Load + {$EXTERNALSYM SERVICETYPE_GUARANTEED} + SERVICETYPE_GUARANTEED = $00000003; // Guaranteed + {$EXTERNALSYM SERVICETYPE_NETWORK_UNAVAILABLE} + SERVICETYPE_NETWORK_UNAVAILABLE = $00000004; // Used to notify change to user + {$EXTERNALSYM SERVICETYPE_GENERAL_INFORMATION} + SERVICETYPE_GENERAL_INFORMATION = $00000005; // corresponds to "General Parameters" defined by IntServ + {$EXTERNALSYM SERVICETYPE_NOCHANGE} + SERVICETYPE_NOCHANGE = $00000006; // used to indicate that the flow spec contains no change from any previous one +// to turn on immediate traffic control, OR this flag with the ServiceType field in the FLOWSPEC + {$EXTERNALSYM SERVICE_IMMEDIATE_TRAFFIC_CONTROL} + SERVICE_IMMEDIATE_TRAFFIC_CONTROL = $80000000; + +// WinSock 2 extension -- manifest constants for return values of the condition function + {$EXTERNALSYM CF_ACCEPT} + CF_ACCEPT = $0000; + {$EXTERNALSYM CF_REJECT} + CF_REJECT = $0001; + {$EXTERNALSYM CF_DEFER} + CF_DEFER = $0002; + +// WinSock 2 extension -- manifest constants for shutdown() + {$EXTERNALSYM SD_RECEIVE} + SD_RECEIVE = $00; + {$EXTERNALSYM SD_SEND} + SD_SEND = $01; + {$EXTERNALSYM SD_BOTH} + SD_BOTH = $02; + +// WinSock 2 extension -- data type and manifest constants for socket groups + {$EXTERNALSYM SG_UNCONSTRAINED_GROUP} + SG_UNCONSTRAINED_GROUP = $01; + {$EXTERNALSYM SG_CONSTRAINED_GROUP} + SG_CONSTRAINED_GROUP = $02; + +type + {$EXTERNALSYM GROUP} + GROUP = DWORD; + +// WinSock 2 extension -- data type for WSAEnumNetworkEvents() + {$EXTERNALSYM WSANETWORKEVENTS} + WSANETWORKEVENTS = record + lNetworkEvents: LongInt; + iErrorCode: Array[0..FD_MAX_EVENTS-1] of Integer; + end; + {$NODEFINE TWSANetworkEvents} + TWSANetworkEvents = WSANETWORKEVENTS; + {$NODEFINE PWSANetworkEvents} + PWSANetworkEvents = ^TWSANetworkEvents; + {$EXTERNALSYM LPWSANETWORKEVENTS} + LPWSANETWORKEVENTS = PWSANetworkEvents; + +//TransmitFile types used for the TransmitFile API function in WinNT/2000/XP +//not sure why its defined in WinCE when TransmitFile is not available. + {$IFNDEF NO_REDECLARE} + {$EXTERNALSYM TRANSMIT_FILE_BUFFERS} + TRANSMIT_FILE_BUFFERS = record + Head: Pointer; + HeadLength: DWORD; + Tail: Pointer; + TailLength: DWORD; + end; + {$NODEFINE TTransmitFileBuffers} + TTransmitFileBuffers = TRANSMIT_FILE_BUFFERS; + {$NODEFINE PTransmitFileBuffers} + PTransmitFileBuffers = ^TTransmitFileBuffers; + {$ENDIF} + {$EXTERNALSYM LPTRANSMIT_FILE_BUFFERS} + LPTRANSMIT_FILE_BUFFERS = PTransmitFileBuffers; + +const + {$EXTERNALSYM TP_ELEMENT_MEMORY} + TP_ELEMENT_MEMORY = 1; + {$EXTERNALSYM TP_ELEMENT_FILE} + TP_ELEMENT_FILE = 2; + {$EXTERNALSYM TP_ELEMENT_EOP} + TP_ELEMENT_EOP = 4; + + {$EXTERNALSYM TP_DISCONNECT} + TP_DISCONNECT = TF_DISCONNECT; + {$EXTERNALSYM TP_REUSE_SOCKET} + TP_REUSE_SOCKET = TF_REUSE_SOCKET; + {$EXTERNALSYM TP_USE_DEFAULT_WORKER} + TP_USE_DEFAULT_WORKER = TF_USE_DEFAULT_WORKER; + {$EXTERNALSYM TP_USE_SYSTEM_THREAD} + TP_USE_SYSTEM_THREAD = TF_USE_SYSTEM_THREAD; + {$EXTERNALSYM TP_USE_KERNEL_APC} + TP_USE_KERNEL_APC = TF_USE_KERNEL_APC; + +type + {$EXTERNALSYM TRANSMIT_PACKETS_ELEMENT} + TRANSMIT_PACKETS_ELEMENT = record + dwElFlags: ULONG; + cLength: ULONG; + case Integer of + 1: (nFileOffset: TLargeInteger; + hFile: THandle); + 2: (pBuffer: Pointer); + end; + {$NODEFINE TTransmitPacketsElement} + TTransmitPacketsElement = TRANSMIT_PACKETS_ELEMENT; + {$NODEFINE PTransmitPacketsElement} + PTransmitPacketsElement = ^TTransmitPacketsElement; + {$NODEFINE LPTransmitPacketsElement} + LPTransmitPacketsElement = PTransmitPacketsElement; + + {$EXTERNALSYM PTRANSMIT_PACKETS_ELEMENT} + PTRANSMIT_PACKETS_ELEMENT = ^TTransmitPacketsElement; + {$EXTERNALSYM LPTRANSMIT_PACKETS_ELEMENT} + LPTRANSMIT_PACKETS_ELEMENT = PTRANSMIT_PACKETS_ELEMENT; + +// WinSock 2 extension -- WSAPROTOCOL_INFO structure + +{$IFNDEF HAS_LPGUID} +type + {$IFNDEF HAS_PGUID} + {$NODEFINE PGUID} + PGUID = ^TGUID; + {$ENDIF} + {$EXTERNALSYM LPGUID} + LPGUID = PGUID; +{$ENDIF} + +// WinSock 2 extension -- WSAPROTOCOL_INFO manifest constants + +const + {$EXTERNALSYM MAX_PROTOCOL_CHAIN} + MAX_PROTOCOL_CHAIN = 7; + {$EXTERNALSYM BASE_PROTOCOL} + BASE_PROTOCOL = 1; + {$EXTERNALSYM LAYERED_PROTOCOL} + LAYERED_PROTOCOL = 0; + {$EXTERNALSYM WSAPROTOCOL_LEN} + WSAPROTOCOL_LEN = 255; + +type + {$EXTERNALSYM WSAPROTOCOLCHAIN} + WSAPROTOCOLCHAIN = record + ChainLen: Integer; // the length of the chain, + // length = 0 means layered protocol, + // length = 1 means base protocol, + // length > 1 means protocol chain + ChainEntries: Array[0..MAX_PROTOCOL_CHAIN-1] of LongInt; // a list of dwCatalogEntryIds + end; + {$NODEFINE TWSAProtocolChain} + TWSAProtocolChain = WSAPROTOCOLCHAIN; + {$EXTERNALSYM LPWSAPROTOCOLCHAIN} + LPWSAPROTOCOLCHAIN = ^TWSAProtocolChain; + +type + {$EXTERNALSYM WSAPROTOCOL_INFOA} + WSAPROTOCOL_INFOA = record + dwServiceFlags1: DWORD; + dwServiceFlags2: DWORD; + dwServiceFlags3: DWORD; + dwServiceFlags4: DWORD; + dwProviderFlags: DWORD; + ProviderId: TGUID; + dwCatalogEntryId: DWORD; + ProtocolChain: TWSAProtocolChain; + iVersion: Integer; + iAddressFamily: Integer; + iMaxSockAddr: Integer; + iMinSockAddr: Integer; + iSocketType: Integer; + iProtocol: Integer; + iProtocolMaxOffset: Integer; + iNetworkByteOrder: Integer; + iSecurityScheme: Integer; + dwMessageSize: DWORD; + dwProviderReserved: DWORD; + szProtocol: Array[0..WSAPROTOCOL_LEN+1-1] of AnsiChar; + end; + {$NODEFINE TWSAProtocol_InfoA} + TWSAProtocol_InfoA = WSAPROTOCOL_INFOA; + {$NODEFINE PWSAProtocol_InfoA} + PWSAProtocol_InfoA = ^WSAPROTOCOL_INFOA; + {$EXTERNALSYM LPWSAPROTOCOL_INFOA} + LPWSAPROTOCOL_INFOA = PWSAProtocol_InfoA; + + {$EXTERNALSYM WSAPROTOCOL_INFOW} + WSAPROTOCOL_INFOW = record + dwServiceFlags1: DWORD; + dwServiceFlags2: DWORD; + dwServiceFlags3: DWORD; + dwServiceFlags4: DWORD; + dwProviderFlags: DWORD; + ProviderId: TGUID; + dwCatalogEntryId: DWORD; + ProtocolChain: TWSAProtocolChain; + iVersion: Integer; + iAddressFamily: Integer; + iMaxSockAddr: Integer; + iMinSockAddr: Integer; + iSocketType: Integer; + iProtocol: Integer; + iProtocolMaxOffset: Integer; + iNetworkByteOrder: Integer; + iSecurityScheme: Integer; + dwMessageSize: DWORD; + dwProviderReserved: DWORD; + szProtocol: Array[0..WSAPROTOCOL_LEN+1-1] of WideChar; + end; + {$NODEFINE TWSAProtocol_InfoW} + TWSAProtocol_InfoW = WSAPROTOCOL_INFOW; + {$NODEFINE PWSAProtocol_InfoW} + PWSAProtocol_InfoW = ^TWSAProtocol_InfoW; + {$EXTERNALSYM LPWSAPROTOCOL_INFOW} + LPWSAPROTOCOL_INFOW = PWSAProtocol_InfoW; + + {$EXTERNALSYM WSAPROTOCOL_INFO} + {$EXTERNALSYM LPWSAPROTOCOL_INFO} + {$NODEFINE TWSAProtocol_Info} + {$NODEFINE PWSAProtocol_Info} + {$IFDEF UNICODE} + WSAPROTOCOL_INFO = TWSAProtocol_InfoW; + TWSAProtocol_Info = TWSAProtocol_InfoW; + PWSAProtocol_Info = PWSAProtocol_InfoW; + LPWSAPROTOCOL_INFO = PWSAProtocol_InfoW; + {$ELSE} + WSAPROTOCOL_INFO = TWSAProtocol_InfoA; + TWSAProtocol_Info = TWSAProtocol_InfoA; + PWSAProtocol_Info = PWSAProtocol_InfoA; + LPWSAPROTOCOL_INFO = PWSAProtocol_InfoA; + {$ENDIF} + +const +// flag bit definitions for dwProviderFlags + {$EXTERNALSYM PFL_MULTIPLE_PROTO_ENTRIES} + PFL_MULTIPLE_PROTO_ENTRIES = $00000001; + {$EXTERNALSYM PFL_RECOMMENTED_PROTO_ENTRY} + PFL_RECOMMENTED_PROTO_ENTRY = $00000002; + {$EXTERNALSYM PFL_HIDDEN} + PFL_HIDDEN = $00000004; + {$EXTERNALSYM PFL_MATCHES_PROTOCOL_ZERO} + PFL_MATCHES_PROTOCOL_ZERO = $00000008; + +// flag bit definitions for dwServiceFlags1 + {$EXTERNALSYM XP1_CONNECTIONLESS} + XP1_CONNECTIONLESS = $00000001; + {$EXTERNALSYM XP1_GUARANTEED_DELIVERY} + XP1_GUARANTEED_DELIVERY = $00000002; + {$EXTERNALSYM XP1_GUARANTEED_ORDER} + XP1_GUARANTEED_ORDER = $00000004; + {$EXTERNALSYM XP1_MESSAGE_ORIENTED} + XP1_MESSAGE_ORIENTED = $00000008; + {$EXTERNALSYM XP1_PSEUDO_STREAM} + XP1_PSEUDO_STREAM = $00000010; + {$EXTERNALSYM XP1_GRACEFUL_CLOSE} + XP1_GRACEFUL_CLOSE = $00000020; + {$EXTERNALSYM XP1_EXPEDITED_DATA} + XP1_EXPEDITED_DATA = $00000040; + {$EXTERNALSYM XP1_CONNECT_DATA} + XP1_CONNECT_DATA = $00000080; + {$EXTERNALSYM XP1_DISCONNECT_DATA} + XP1_DISCONNECT_DATA = $00000100; + {$EXTERNALSYM XP1_SUPPORT_BROADCAST} + XP1_SUPPORT_BROADCAST = $00000200; + {$EXTERNALSYM XP1_SUPPORT_MULTIPOINT} + XP1_SUPPORT_MULTIPOINT = $00000400; + {$EXTERNALSYM XP1_MULTIPOINT_CONTROL_PLANE} + XP1_MULTIPOINT_CONTROL_PLANE = $00000800; + {$EXTERNALSYM XP1_MULTIPOINT_DATA_PLANE} + XP1_MULTIPOINT_DATA_PLANE = $00001000; + {$EXTERNALSYM XP1_QOS_SUPPORTED} + XP1_QOS_SUPPORTED = $00002000; + {$EXTERNALSYM XP1_INTERRUPT} + XP1_INTERRUPT = $00004000; + {$EXTERNALSYM XP1_UNI_SEND} + XP1_UNI_SEND = $00008000; + {$EXTERNALSYM XP1_UNI_RECV} + XP1_UNI_RECV = $00010000; + {$EXTERNALSYM XP1_IFS_HANDLES} + XP1_IFS_HANDLES = $00020000; + {$EXTERNALSYM XP1_PARTIAL_MESSAGE} + XP1_PARTIAL_MESSAGE = $00040000; + + {$EXTERNALSYM BIGENDIAN} + BIGENDIAN = $0000; + {$EXTERNALSYM LITTLEENDIAN} + LITTLEENDIAN = $0001; + + {$EXTERNALSYM SECURITY_PROTOCOL_NONE} + SECURITY_PROTOCOL_NONE = $0000; + +// WinSock 2 extension -- manifest constants for WSAJoinLeaf() + {$EXTERNALSYM JL_SENDER_ONLY} + JL_SENDER_ONLY = $01; + {$EXTERNALSYM JL_RECEIVER_ONLY} + JL_RECEIVER_ONLY = $02; + {$EXTERNALSYM JL_BOTH} + JL_BOTH = $04; + +// WinSock 2 extension -- manifest constants for WSASocket() + {$EXTERNALSYM WSA_FLAG_OVERLAPPED} + WSA_FLAG_OVERLAPPED = $01; + {$EXTERNALSYM WSA_FLAG_MULTIPOINT_C_ROOT} + WSA_FLAG_MULTIPOINT_C_ROOT = $02; + {$EXTERNALSYM WSA_FLAG_MULTIPOINT_C_LEAF} + WSA_FLAG_MULTIPOINT_C_LEAF = $04; + {$EXTERNALSYM WSA_FLAG_MULTIPOINT_D_ROOT} + WSA_FLAG_MULTIPOINT_D_ROOT = $08; + {$EXTERNALSYM WSA_FLAG_MULTIPOINT_D_LEAF} + WSA_FLAG_MULTIPOINT_D_LEAF = $10; + +// WinSock 2 extension -- manifest constants for WSAIoctl() + {$EXTERNALSYM IOC_UNIX} + IOC_UNIX = $00000000; + {$EXTERNALSYM IOC_WS2} + IOC_WS2 = $08000000; + {$EXTERNALSYM IOC_PROTOCOL} + IOC_PROTOCOL = $10000000; + {$EXTERNALSYM IOC_VENDOR} + IOC_VENDOR = $18000000; + + {$IFNDEF WINCE} +///* +// * WSK-specific IO control codes are Winsock2 codes with the highest-order +// * 3 bits of the Vendor/AddressFamily-specific field set to 1. +// */ + {$EXTERNALSYM IOC_WSK} + IOC_WSK = IOC_WS2 or $07000000; + {$ENDIF} + {$EXTERNALSYM SIO_ASSOCIATE_HANDLE} + SIO_ASSOCIATE_HANDLE = DWORD(IOC_IN or IOC_WS2 or 1); + {$EXTERNALSYM SIO_ENABLE_CIRCULAR_QUEUEING} + SIO_ENABLE_CIRCULAR_QUEUEING = DWORD(IOC_VOID or IOC_WS2 or 2); + {$EXTERNALSYM SIO_FIND_ROUTE} + SIO_FIND_ROUTE = DWORD(IOC_OUT or IOC_WS2 or 3); + {$EXTERNALSYM SIO_FLUSH} + SIO_FLUSH = DWORD(IOC_VOID or IOC_WS2 or 4); + {$EXTERNALSYM SIO_GET_BROADCAST_ADDRESS} + SIO_GET_BROADCAST_ADDRESS = DWORD(IOC_OUT or IOC_WS2 or 5); + {$EXTERNALSYM SIO_GET_EXTENSION_FUNCTION_POINTER} + SIO_GET_EXTENSION_FUNCTION_POINTER = DWORD(IOC_INOUT or IOC_WS2 or 6); + {$EXTERNALSYM SIO_GET_QOS} + SIO_GET_QOS = DWORD(IOC_INOUT or IOC_WS2 or 7); + {$EXTERNALSYM SIO_GET_GROUP_QOS} + SIO_GET_GROUP_QOS = DWORD(IOC_INOUT or IOC_WS2 or 8); + {$EXTERNALSYM SIO_MULTIPOINT_LOOPBACK} + SIO_MULTIPOINT_LOOPBACK = DWORD(IOC_IN or IOC_WS2 or 9); + {$EXTERNALSYM SIO_MULTICAST_SCOPE} + SIO_MULTICAST_SCOPE = DWORD(IOC_IN or IOC_WS2 or 10); + {$EXTERNALSYM SIO_SET_QOS} + SIO_SET_QOS = DWORD(IOC_IN or IOC_WS2 or 11); + {$EXTERNALSYM SIO_SET_GROUP_QOS} + SIO_SET_GROUP_QOS = DWORD(IOC_IN or IOC_WS2 or 12); + {$EXTERNALSYM SIO_TRANSLATE_HANDLE} + SIO_TRANSLATE_HANDLE = DWORD(IOC_INOUT or IOC_WS2 or 13); + {$EXTERNALSYM SIO_ROUTING_INTERFACE_QUERY} + SIO_ROUTING_INTERFACE_QUERY = DWORD(IOC_INOUT or IOC_WS2 or 20); + {$EXTERNALSYM SIO_ROUTING_INTERFACE_CHANGE} + SIO_ROUTING_INTERFACE_CHANGE = DWORD(IOC_IN or IOC_WS2 or 21); + {$EXTERNALSYM SIO_ADDRESS_LIST_QUERY} + SIO_ADDRESS_LIST_QUERY = DWORD(IOC_OUT or IOC_WS2 or 22); // see below SOCKET_ADDRESS_LIST + {$EXTERNALSYM SIO_ADDRESS_LIST_CHANGE} + SIO_ADDRESS_LIST_CHANGE = DWORD(IOC_VOID or IOC_WS2 or 23); + {$EXTERNALSYM SIO_QUERY_TARGET_PNP_HANDLE} + SIO_QUERY_TARGET_PNP_HANDLE = DWORD(IOC_OUT or IOC_WS2 or 24); + {$EXTERNALSYM SIO_NSP_NOTIFY_CHANGE} + SIO_NSP_NOTIFY_CHANGE = DWORD(IOC_IN or IOC_WS2 or 25); + {$EXTERNALSYM SIO_ADDRESS_LIST_SORT} + SIO_ADDRESS_LIST_SORT = DWORD(IOC_INOUT or IOC_WS2 or 25); + {$IFNDEF WINCE} + {$EXTERNALSYM SIO_RESERVED_1} + SIO_RESERVED_1 = DWORD(IOC_IN or IOC_WS2 or 26); + {$EXTERNALSYM SIO_RESERVED_2} + SIO_RESERVED_2 = DWORD(IOC_IN or IOC_WS2 or 33); + {$ENDIF} + +// WinSock 2 extension -- manifest constants for SIO_TRANSLATE_HANDLE ioctl + {$EXTERNALSYM TH_NETDEV} + TH_NETDEV = $00000001; + {$EXTERNALSYM TH_TAPI} + TH_TAPI = $00000002; + +type +// Manifest constants and type definitions related to name resolution and +// registration (RNR) API + {$IFNDEF NO_REDECLARE} + {$EXTERNALSYM BLOB} + BLOB = record + cbSize : U_LONG; + pBlobData : PBYTE; + end; + {$NODEFINE TBLOB} + TBLOB = BLOB; + {$NODEFINE PBLOB} + PBLOB = ^TBLOB; + {$ENDIF} + {$EXTERNALSYM LPBLOB} + LPBLOB = PBLOB; + +// Service Install Flags + +const + {$EXTERNALSYM SERVICE_MULTIPLE} + SERVICE_MULTIPLE = $00000001; + +// & name spaces + {$EXTERNALSYM NS_ALL} + NS_ALL = 0; + + {$EXTERNALSYM NS_SAP} + NS_SAP = 1; + {$EXTERNALSYM NS_NDS} + NS_NDS = 2; + {$EXTERNALSYM NS_PEER_BROWSE} + NS_PEER_BROWSE = 3; + {$EXTERNALSYM NS_SLP} + NS_SLP = 5; + {$EXTERNALSYM NS_DHCP} + NS_DHCP = 6; + + {$EXTERNALSYM NS_TCPIP_LOCAL} + NS_TCPIP_LOCAL = 10; + {$EXTERNALSYM NS_TCPIP_HOSTS} + NS_TCPIP_HOSTS = 11; + {$EXTERNALSYM NS_DNS} + NS_DNS = 12; + {$EXTERNALSYM NS_NETBT} + NS_NETBT = 13; + {$EXTERNALSYM NS_WINS} + NS_WINS = 14; + {$EXTERNALSYM NS_NLA} + NS_NLA = 15; //* Network Location Awareness*/ - WindowsXP + {$EXTERNALSYM NS_BTH} + NS_BTH = 16; //* Bluetooth SDP Namespace */ - Winapi.Windows Vista + + {$EXTERNALSYM NS_NBP} + NS_NBP = 20; + + {$EXTERNALSYM NS_MS} + NS_MS = 30; + {$EXTERNALSYM NS_STDA} + NS_STDA = 31; + {$EXTERNALSYM NS_NTDS} + NS_NTDS = 32; + + //Winapi.Windows Vista namespaces + {$EXTERNALSYM NS_EMAIL} + NS_EMAIL = 37; + {$EXTERNALSYM NS_PNRPNAME} + NS_PNRPNAME = 38; + {$EXTERNALSYM NS_PNRPCLOUD} + NS_PNRPCLOUD = 39; + // + + {$EXTERNALSYM NS_X500} + NS_X500 = 40; + {$EXTERNALSYM NS_NIS} + NS_NIS = 41; + {$EXTERNALSYM NS_NISPLUS} + NS_NISPLUS = 42; + + {$EXTERNALSYM NS_WRQ} + NS_WRQ = 50; + + {$EXTERNALSYM NS_NETDES} + NS_NETDES = 60; // Network Designers Limited + +{ Resolution flags for WSAGetAddressByName(). + Note these are also used by the 1.1 API GetAddressByName, so leave them around. } + {$EXTERNALSYM RES_UNUSED_1} + RES_UNUSED_1 = $00000001; + {$EXTERNALSYM RES_FLUSH_CACHE} + RES_FLUSH_CACHE = $00000002; + {$EXTERNALSYM RES_SERVICE} + RES_SERVICE = $00000004; + +{ Well known value names for Service Types } + {$EXTERNALSYM SERVICE_TYPE_VALUE_IPXPORTA} + SERVICE_TYPE_VALUE_IPXPORTA : PAnsiChar = 'IpxSocket'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_SAPIDA} + SERVICE_TYPE_VALUE_SAPIDA : PAnsiChar = 'SapId'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_TCPPORTA} + SERVICE_TYPE_VALUE_TCPPORTA : PAnsiChar = 'TcpPort'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_UDPPORTA} + SERVICE_TYPE_VALUE_UDPPORTA : PAnsiChar = 'UdpPort'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_OBJECTIDA} + SERVICE_TYPE_VALUE_OBJECTIDA : PAnsiChar = 'ObjectId'; {Do not Localize} + + {$EXTERNALSYM SERVICE_TYPE_VALUE_IPXPORTW} + SERVICE_TYPE_VALUE_IPXPORTW : PWideChar = 'IpxSocket'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_SAPIDW} + SERVICE_TYPE_VALUE_SAPIDW : PWideChar = 'SapId'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_TCPPORTW} + SERVICE_TYPE_VALUE_TCPPORTW : PWideChar = 'TcpPort'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_UDPPORTW} + SERVICE_TYPE_VALUE_UDPPORTW : PWideChar = 'UdpPort'; {Do not Localize} + {$EXTERNALSYM SERVICE_TYPE_VALUE_OBJECTIDW} + SERVICE_TYPE_VALUE_OBJECTIDW : PWideChar = 'ObjectId'; {Do not Localize} + + {$EXTERNALSYM SERVICE_TYPE_VALUE_SAPID} + {$EXTERNALSYM SERVICE_TYPE_VALUE_TCPPORT} + {$EXTERNALSYM SERVICE_TYPE_VALUE_UDPPORT} + {$EXTERNALSYM SERVICE_TYPE_VALUE_OBJECTID} + {$IFDEF UNICODE} + SERVICE_TYPE_VALUE_SAPID : PWideChar = 'SapId'; {Do not Localize} + SERVICE_TYPE_VALUE_TCPPORT : PWideChar = 'TcpPort'; {Do not Localize} + SERVICE_TYPE_VALUE_UDPPORT : PWideChar = 'UdpPort'; {Do not Localize} + SERVICE_TYPE_VALUE_OBJECTID : PWideChar = 'ObjectId'; {Do not Localize} + {$ELSE} + SERVICE_TYPE_VALUE_SAPID : PAnsiChar = 'SapId'; {Do not Localize} + SERVICE_TYPE_VALUE_TCPPORT : PAnsiChar = 'TcpPort'; {Do not Localize} + SERVICE_TYPE_VALUE_UDPPORT : PAnsiChar = 'UdpPort'; {Do not Localize} + SERVICE_TYPE_VALUE_OBJECTID : PAnsiChar = 'ObjectId'; {Do not Localize} + {$ENDIF} + +// SockAddr Information +type + {$EXTERNALSYM SOCKET_ADDRESS} + SOCKET_ADDRESS = record + lpSockaddr : PSOCKADDR; + iSockaddrLength : Integer; + end; + {$NODEFINE TSocket_Address} + TSocket_Address = SOCKET_ADDRESS; + {$EXTERNALSYM PSOCKET_ADDRESS} + PSOCKET_ADDRESS = ^TSocket_Address; + + {$EXTERNALSYM SOCKET_ADDRESS_LIST} + SOCKET_ADDRESS_LIST = record + iAddressCount : Integer; + Address : SOCKET_ADDRESS; + end; + {$NODEFINE TSocket_Address_List} + TSocket_Address_List = SOCKET_ADDRESS_LIST; + {$EXTERNALSYM PSOCKET_ADDRESS_LIST} + PSOCKET_ADDRESS_LIST = ^TSocket_Address_List; + {$EXTERNALSYM LPSOCKET_ADDRESS_LIST} + LPSOCKET_ADDRESS_LIST = PSOCKET_ADDRESS_LIST; + +// CSAddr Information + {$EXTERNALSYM CSADDR_INFO} + CSADDR_INFO = record + LocalAddr, + RemoteAddr : TSocket_Address; + iSocketType, + iProtocol : Integer; + end; + {$NODEFINE TCSAddr_Info} + TCSAddr_Info = CSADDR_INFO; + {$EXTERNALSYM PCSADDR_INFO} + PCSADDR_INFO = ^TCSAddr_Info; + {$EXTERNALSYM LPCSADDR_INFO} + LPCSADDR_INFO = PCSADDR_INFO; + +// Address Family/Protocol Tuples + {$EXTERNALSYM AFPROTOCOLS} + AFPROTOCOLS = record + iAddressFamily : Integer; + iProtocol : Integer; + end; + {$NODEFINE TAFProtocols} + TAFProtocols = AFPROTOCOLS; + {$EXTERNALSYM PAFPROTOCOLS} + PAFPROTOCOLS = ^TAFProtocols; + {$EXTERNALSYM LPAFPROTOCOLS} + LPAFPROTOCOLS = PAFPROTOCOLS; + +// Client Query API Typedefs + +// The comparators + {$EXTERNALSYM WSAECOMPARATOR} + WSAECOMPARATOR = (COMP_EQUAL {= 0}, COMP_NOTLESS); + {$NODEFINE TWSAEComparator} + TWSAEComparator = WSAECOMPARATOR; + {$EXTERNALSYM PWSAECOMPARATOR} + PWSAECOMPARATOR = ^WSAECOMPARATOR; + + {$EXTERNALSYM WSAVERSION} + WSAVERSION = record + dwVersion : DWORD; + ecHow : TWSAEComparator; + end; + {$NODEFINE TWSAVersion} + TWSAVersion = WSAVERSION; + {$EXTERNALSYM PWSAVERSION} + PWSAVERSION = ^TWSAVersion; + {$EXTERNALSYM LPWSAVERSION} + LPWSAVERSION = PWSAVERSION; + + {$EXTERNALSYM WSAQUERYSETA} + WSAQUERYSETA = record + dwSize : DWORD; + lpszServiceInstanceName : PAnsiChar; + lpServiceClassId : PGUID; + lpVersion : LPWSAVERSION; + lpszComment : PAnsiChar; + dwNameSpace : DWORD; + lpNSProviderId : PGUID; + lpszContext : PAnsiChar; + dwNumberOfProtocols : DWORD; + lpafpProtocols : LPAFPROTOCOLS; + lpszQueryString : PAnsiChar; + dwNumberOfCsAddrs : DWORD; + lpcsaBuffer : LPCSADDR_INFO; + dwOutputFlags : DWORD; + lpBlob : LPBLOB; + end; + {$NODEFINE TWSAQuerySetA} + TWSAQuerySetA = WSAQUERYSETA; + {$EXTERNALSYM PWSAQUERYSETA} + PWSAQUERYSETA = ^TWSAQuerySetA; + {$EXTERNALSYM LPWSAQUERYSETA} + LPWSAQUERYSETA = PWSAQUERYSETA; + + {$EXTERNALSYM WSAQUERYSETW} + WSAQUERYSETW = record + dwSize : DWORD; + lpszServiceInstanceName : PWideChar; + lpServiceClassId : PGUID; + lpVersion : LPWSAVERSION; + lpszComment : PWideChar; + dwNameSpace : DWORD; + lpNSProviderId : PGUID; + lpszContext : PWideChar; + dwNumberOfProtocols : DWORD; + lpafpProtocols : LPAFPROTOCOLS; + lpszQueryString : PWideChar; + dwNumberOfCsAddrs : DWORD; + lpcsaBuffer : LPCSADDR_INFO; + dwOutputFlags : DWORD; + lpBlob : LPBLOB; + end; + {$NODEFINE TWSAQuerySetW} + TWSAQuerySetW = WSAQUERYSETW; + {$EXTERNALSYM PWSAQUERYSETW} + PWSAQUERYSETW = ^TWSAQuerySetW; + {$EXTERNALSYM LPWSAQUERYSETW} + LPWSAQUERYSETW = PWSAQUERYSETW; + + {$NODEFINE TWSAQuerySet} + {$EXTERNALSYM PWSAQUERYSET} + {$EXTERNALSYM LPWSAQUERYSET} + {$IFDEF UNICODE} + TWSAQuerySet = TWSAQuerySetW; + PWSAQUERYSET = PWSAQUERYSETW; + LPWSAQUERYSET = LPWSAQUERYSETW; + {$ELSE} + TWSAQuerySet = TWSAQuerySetA; + PWSAQUERYSET = PWSAQUERYSETA; + LPWSAQUERYSET = LPWSAQUERYSETA; + {$ENDIF} + +const + {$EXTERNALSYM LUP_DEEP} + LUP_DEEP = $0001; + {$EXTERNALSYM LUP_CONTAINERS} + LUP_CONTAINERS = $0002; + {$EXTERNALSYM LUP_NOCONTAINERS} + LUP_NOCONTAINERS = $0004; + {$EXTERNALSYM LUP_NEAREST} + LUP_NEAREST = $0008; + {$EXTERNALSYM LUP_RETURN_NAME} + LUP_RETURN_NAME = $0010; + {$EXTERNALSYM LUP_RETURN_TYPE} + LUP_RETURN_TYPE = $0020; + {$EXTERNALSYM LUP_RETURN_VERSION} + LUP_RETURN_VERSION = $0040; + {$EXTERNALSYM LUP_RETURN_COMMENT} + LUP_RETURN_COMMENT = $0080; + {$EXTERNALSYM LUP_RETURN_ADDR} + LUP_RETURN_ADDR = $0100; + {$EXTERNALSYM LUP_RETURN_BLOB} + LUP_RETURN_BLOB = $0200; + {$EXTERNALSYM LUP_RETURN_ALIASES} + LUP_RETURN_ALIASES = $0400; + {$EXTERNALSYM LUP_RETURN_QUERY_STRING} + LUP_RETURN_QUERY_STRING = $0800; + {$EXTERNALSYM LUP_RETURN_ALL} + LUP_RETURN_ALL = $0FF0; + {$EXTERNALSYM LUP_RES_SERVICE} + LUP_RES_SERVICE = $8000; + + {$EXTERNALSYM LUP_FLUSHCACHE} + LUP_FLUSHCACHE = $1000; + {$EXTERNALSYM LUP_FLUSHPREVIOUS} + LUP_FLUSHPREVIOUS = $2000; + +// Return flags + {$EXTERNALSYM RESULT_IS_ALIAS} + RESULT_IS_ALIAS = $0001; + //These are not supported in WinCE 4.2 but are available in later versions. + {$EXTERNALSYM RESULT_IS_ADDED} + RESULT_IS_ADDED = $0010; + {$EXTERNALSYM RESULT_IS_CHANGED} + RESULT_IS_CHANGED = $0020; + {$EXTERNALSYM RESULT_IS_DELETED} + RESULT_IS_DELETED = $0040; + + {$EXTERNALSYM MAX_NATURAL_ALIGNMENT} + {$IFDEF _WIN64} + MAX_NATURAL_ALIGNMENT = SizeOf(Int64); + {$ELSE} + MAX_NATURAL_ALIGNMENT = SizeOf(DWORD); + {$ENDIF} + +// WSARecvMsg flags + {$EXTERNALSYM MSG_TRUNC} + MSG_TRUNC = $0100; + {$EXTERNALSYM MSG_CTRUNC} + MSG_CTRUNC = $0200; + {$EXTERNALSYM MSG_BCAST} + MSG_BCAST = $0400; + {$EXTERNALSYM MSG_MCAST} + MSG_MCAST = $0800; + +{$IFNDEF WINCE} + //Winapi.Windows Vista WSAPoll +//* Event flag definitions for WSAPoll(). */ + {$EXTERNALSYM POLLRDNORM} + POLLRDNORM = $0100; + {$EXTERNALSYM POLLRDBAND} + POLLRDBAND = $0200; + {$EXTERNALSYM POLLIN} + POLLIN = (POLLRDNORM or POLLRDBAND); + {$EXTERNALSYM POLLPRI} + POLLPRI = $0400; + {$EXTERNALSYM POLLWRNORM} + POLLWRNORM = $0010; + {$EXTERNALSYM POLLOUT} + POLLOUT = (POLLWRNORM); + {$EXTERNALSYM POLLWRBAND} + POLLWRBAND = $0020; + {$EXTERNALSYM POLLERR} + POLLERR = $0001; + {$EXTERNALSYM POLLHUP} + POLLHUP = $0002; + {$EXTERNALSYM POLLNVAL} + POLLNVAL = $0004; +{$ENDIF} + +type +// Service Address Registration and Deregistration Data Types. + {$EXTERNALSYM WSAESETSERVICEOP} + WSAESETSERVICEOP = (RNRSERVICE_REGISTER{=0}, RNRSERVICE_DEREGISTER, RNRSERVICE_DELETE); + {$NODEFINE TWSAESetServiceOp} + TWSAESetServiceOp = WSAESETSERVICEOP; + +{ Service Installation/Removal Data Types. } + {$EXTERNALSYM WSANSCLASSINFOA} + WSANSCLASSINFOA = record + lpszName : PAnsiChar; + dwNameSpace : DWORD; + dwValueType : DWORD; + dwValueSize : DWORD; + lpValue : Pointer; + end; + {$NODEFINE TWSANSClassInfoA} + TWSANSClassInfoA = WSANSCLASSINFOA; + {$EXTERNALSYM PWSANSClassInfoA} + PWSANSCLASSINFOA = ^TWSANSClassInfoA; + {$EXTERNALSYM LPWSANSCLASSINFOA} + LPWSANSCLASSINFOA = PWSANSCLASSINFOA; + + {$EXTERNALSYM WSANSCLASSINFOW} + WSANSCLASSINFOW = record + lpszName : PWideChar; + dwNameSpace : DWORD; + dwValueType : DWORD; + dwValueSize : DWORD; + lpValue : Pointer; + end; + {$NODEFINE TWSANSClassInfoW} + TWSANSClassInfoW = WSANSCLASSINFOW; + {$EXTERNALSYM PWSANSClassInfoW} + PWSANSCLASSINFOW = ^TWSANSClassInfoW; + {$EXTERNALSYM LPWSANSCLASSINFOW} + LPWSANSCLASSINFOW = PWSANSCLASSINFOW; + + {$NODEFINE TWSANSClassInfo} + {$EXTERNALSYM WSANSCLASSINFO} + {$EXTERNALSYM PWSANSCLASSINFO} + {$EXTERNALSYM LPWSANSCLASSINFO} + {$IFDEF UNICODE} + TWSANSClassInfo = TWSANSClassInfoW; + WSANSCLASSINFO = TWSANSClassInfoW; + PWSANSCLASSINFO = PWSANSCLASSINFOW; + LPWSANSCLASSINFO = LPWSANSCLASSINFOW; + {$ELSE} + TWSANSClassInfo = TWSANSClassInfoA; + WSANSCLASSINFO = TWSANSClassInfoA; + PWSANSCLASSINFO = PWSANSCLASSINFOA; + LPWSANSCLASSINFO = LPWSANSCLASSINFOA; + {$ENDIF // UNICODE} + + {$EXTERNALSYM WSASERVICECLASSINFOA} + WSASERVICECLASSINFOA = record + lpServiceClassId : PGUID; + lpszServiceClassName : PAnsiChar; + dwCount : DWORD; + lpClassInfos : LPWSANSCLASSINFOA; + end; + {$NODEFINE TWSAServiceClassInfoA} + TWSAServiceClassInfoA = WSASERVICECLASSINFOA; + {$EXTERNALSYM PWSASERVICECLASSINFOA} + PWSASERVICECLASSINFOA = ^TWSAServiceClassInfoA; + {$EXTERNALSYM LPWSASERVICECLASSINFOA} + LPWSASERVICECLASSINFOA = PWSASERVICECLASSINFOA; + + {$EXTERNALSYM WSASERVICECLASSINFOW} + WSASERVICECLASSINFOW = record + lpServiceClassId : PGUID; + lpszServiceClassName : PWideChar; + dwCount : DWORD; + lpClassInfos : LPWSANSCLASSINFOW; + end; + {$NODEFINE TWSAServiceClassInfoW} + TWSAServiceClassInfoW = WSASERVICECLASSINFOW; + {$EXTERNALSYM PWSASERVICECLASSINFOW} + PWSASERVICECLASSINFOW = ^TWSAServiceClassInfoW; + {$EXTERNALSYM LPWSASERVICECLASSINFOW} + LPWSASERVICECLASSINFOW = PWSASERVICECLASSINFOW; + + {$NODEFINE TWSAServiceClassInfo} + {$EXTERNALSYM WSASERVICECLASSINFO} + {$EXTERNALSYM PWSASERVICECLASSINFO} + {$EXTERNALSYM LPWSASERVICECLASSINFO} + {$IFDEF UNICODE} + TWSAServiceClassInfo = TWSAServiceClassInfoW; + WSASERVICECLASSINFO = TWSAServiceClassInfoW; + PWSASERVICECLASSINFO = PWSASERVICECLASSINFOW; + LPWSASERVICECLASSINFO = LPWSASERVICECLASSINFOW; + {$ELSE} + TWSAServiceClassInfo = TWSAServiceClassInfoA; + WSASERVICECLASSINFO = TWSAServiceClassInfoA; + PWSASERVICECLASSINFO = PWSASERVICECLASSINFOA; + LPWSASERVICECLASSINFO = LPWSASERVICECLASSINFOA; + {$ENDIF} + + {$EXTERNALSYM WSANAMESPACE_INFOA} + WSANAMESPACE_INFOA = record + NSProviderId : TGUID; + dwNameSpace : DWORD; + fActive : DWORD{Bool}; + dwVersion : DWORD; + lpszIdentifier : PAnsiChar; + end; + {$NODEFINE TWSANameSpace_InfoA} + TWSANameSpace_InfoA = WSANAMESPACE_INFOA; + {$EXTERNALSYM PWSANAMESPACE_INFOA} + PWSANAMESPACE_INFOA = ^TWSANameSpace_InfoA; + {$EXTERNALSYM LPWSANAMESPACE_INFOA} + LPWSANAMESPACE_INFOA = PWSANAMESPACE_INFOA; + + {$EXTERNALSYM WSANAMESPACE_INFOW} + WSANAMESPACE_INFOW = record + NSProviderId : TGUID; + dwNameSpace : DWORD; + fActive : DWORD{Bool}; + dwVersion : DWORD; + lpszIdentifier : PWideChar; + end; + {$NODEFINE TWSANameSpace_InfoW} + TWSANameSpace_InfoW = WSANAMESPACE_INFOW; + {$EXTERNALSYM PWSANAMESPACE_INFOW} + PWSANAMESPACE_INFOW = ^TWSANameSpace_InfoW; + {$EXTERNALSYM LPWSANAMESPACE_INFOW} + LPWSANAMESPACE_INFOW = PWSANAMESPACE_INFOW; + +{$IFNDEF WINCE} + {$EXTERNALSYM WSANAMESPACE_INFOEXW} + WSANAMESPACE_INFOEXW = record + NSProviderId : TGUID; + dwNameSpace : DWord; + fActive : LongBool; + lpszIdentifier : LPWSTR; + ProviderSpecific : BLOB; + end; + {$NODEFINE TWSANameSpace_InfoExW} + TWSANameSpace_InfoExW = WSANAMESPACE_INFOEXW; + {$EXTERNALSYM PWSANAMESPACE_INFOEXW} + PWSANAMESPACE_INFOEXW = ^TWSANameSpace_InfoExW; + {$EXTERNALSYM LPWSANAMESPACE_INFOEXW} + LPWSANAMESPACE_INFOEXW = PWSANAMESPACE_INFOEXW; + + {$EXTERNALSYM WSANAMESPACE_INFOEXA} + WSANAMESPACE_INFOEXA = record + NSProviderId : TGUID; + dwNameSpace : DWord; + fActive : LongBool; + lpszIdentifier : LPSTR; + ProviderSpecific : BLOB; + end; + {$NODEFINE TWSANameSpace_InfoExA} + TWSANameSpace_InfoExA = WSANAMESPACE_INFOEXA; + {$EXTERNALSYM PWSANAMESPACE_INFOEXA} + PWSANAMESPACE_INFOEXA = ^TWSANameSpace_InfoExA; + {$EXTERNALSYM LPWSANAMESPACE_INFOEXA} + LPWSANAMESPACE_INFOEXA = PWSANAMESPACE_INFOEXA; + + {$EXTERNALSYM LPFN_WSAENUMNAMESPACEPROVIDERSEXW} + LPFN_WSAENUMNAMESPACEPROVIDERSEXW = function (var lpdwBufferLength : DWord; + lpnspBuffer : PWSANAMESPACE_INFOEXW): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAENUMNAMESPACEPROVIDERSEXA} + LPFN_WSAENUMNAMESPACEPROVIDERSEXA = function (var lpdwBufferLength : DWord; + lpnspBuffer : PWSANAMESPACE_INFOEXA): Integer; stdcall; + + {$NODEFINE TWSANameSpace_InfoEx} + {$EXTERNALSYM WSANAMESPACE_INFOEX} + {$EXTERNALSYM PWSANAMESPACE_INFOEX} + {$EXTERNALSYM LPWSANAMESPACE_INFOEX} + {$EXTERNALSYM LPFN_WSAENUMNAMESPACEPROVIDERSEX} + {$IFDEF UNICODE} + WSANAMESPACE_INFOEX = WSANAMESPACE_INFOEXW; + TWSANameSpace_InfoEx = TWSANameSpace_InfoExW; + PWSANAMESPACE_INFOEX = PWSANAMESPACE_INFOEXW; + LPWSANAMESPACE_INFOEX = PWSANAMESPACE_INFOEX; + LPFN_WSAENUMNAMESPACEPROVIDERSEX = LPFN_WSAENUMNAMESPACEPROVIDERSEXW; + {$ELSE} + WSANAMESPACE_INFOEX = WSANAMESPACE_INFOEXA; + TWSANameSpace_InfoEx = TWSANameSpace_InfoExA; + PWSANAMESPACE_INFOEX = PWSANAMESPACE_INFOEXA; + LPWSANAMESPACE_INFOEX = PWSANAMESPACE_INFOEX; + LPFN_WSAENUMNAMESPACEPROVIDERSEX = LPFN_WSAENUMNAMESPACEPROVIDERSEXA; + {$ENDIF} +{$ENDIF} // WINCE + + {$NODEFINE TWSANameSpace_Info} + {$EXTERNALSYM WSANAMESPACE_INFO} + {$EXTERNALSYM PWSANAMESPACE_INFO} + {$EXTERNALSYM LPWSANAMESPACE_INFO} + {$IFDEF UNICODE} + TWSANameSpace_Info = TWSANameSpace_InfoW; + WSANAMESPACE_INFO = TWSANameSpace_InfoW; + PWSANAMESPACE_INFO = PWSANAMESPACE_INFOW; + LPWSANAMESPACE_INFO = LPWSANAMESPACE_INFOW; + {$ELSE} + TWSANameSpace_Info = TWSANameSpace_InfoA; + WSANAMESPACE_INFO = TWSANameSpace_InfoA; + PWSANAMESPACE_INFO = PWSANAMESPACE_INFOA; + LPWSANAMESPACE_INFO = LPWSANAMESPACE_INFOA; + {$ENDIF} + + {$IFDEF WINCE} + {$EXTERNALSYM DSCP_TRAFFIC_TYPE} + {$EXTERNALSYM DSCPTYPENOTSET} + {$EXTERNALSYM DSCPBESTEFFORT} + {$EXTERNALSYM DSCPBACKGROUND} + {$EXTERNALSYM DSCPEXCELLENTEFFORT} + {$EXTERNALSYM DSCPVIDEO} + {$EXTERNALSYM DSCPAUDIO} + {$EXTERNALSYM DSCPCONTROL} + {$EXTERNALSYM NUMDSCPTRAFFICTYPES} + DSCP_TRAFFIC_TYPE = ( + DSCPTypeNotSet = 0, + DSCPBestEffort = 1, + DSCPBackground = 2, + DSCPExcellentEffort = 3, + DSCPVideo = 4, + DSCPAudio = 5, + DSCPControl = 6); +// Define NumDSCPTrafficTypes as DSCPControl +//because FPC warns that enumerations must be descending. +//The original definition for DSCP_TRAFFIC_TYPE is: +// +///* differential service traffic types */ +//typedef enum _DSCP_TRAFFIC_TYPE +//{ +// DSCPTypeNotSet = 0, +// DSCPBestEffort = 1, +// DSCPBackground = 2, +// DSCPExcellentEffort = 3, +// DSCPVideo = 4, +// DSCPAudio = 5, +// DSCPControl = 6, +// NumDSCPTrafficTypes = 6 +//} //DSCP_TRAFFIC_TYPE; +const + NumDSCPTrafficTypes : DSCP_TRAFFIC_TYPE = DSCPControl; +type + {$ENDIF} + + {$EXTERNALSYM WSAMSG} + WSAMSG = record + name : PSOCKADDR; ///* Remote address */ + namelen : Integer; ///* Remote address length * + lpBuffers : LPWSABUF; // /* Data buffer array */ + dwBufferCount : DWord; // /* Number of elements in the array */ + Control : WSABUF; // /* Control buffer */ + dwFlags : DWord; // /* Flags */ + end; + {$NODEFINE TWSAMSG} + TWSAMSG = WSAMSG; + {$EXTERNALSYM PWSAMSG} + PWSAMSG = ^TWSAMSG; + {$EXTERNALSYM LPWSAMSG} + LPWSAMSG = PWSAMSG; + + {$EXTERNALSYM _WSACMSGHDR} + _WSACMSGHDR = record + cmsg_len: SIZE_T; + cmsg_level: Integer; + cmsg_type: Integer; + { followed by UCHAR cmsg_data[] } + end; + {$EXTERNALSYM WSACMSGHDR} + WSACMSGHDR = _WSACMSGHDR; + {$EXTERNALSYM cmsghdr} + cmsghdr = _WSACMSGHDR; + {$NODEFINE TWSACMsgHdr} + TWSACMsgHdr = WSACMSGHDR; + {$EXTERNALSYM PWSACMSGHDR} + PWSACMSGHDR = ^TWSACMsgHdr; + {$EXTERNALSYM LPWSACMSGHDR} + LPWSACMSGHDR = PWSACMSGHDR; + {$EXTERNALSYM CMSGHDR} + PCMSGHDR = ^CMSGHDR; +{$IFNDEF WINCE} + {$EXTERNALSYM WSAPOLLFD} + WSAPOLLFD = record + fd : TSocket; + events : SHORT; + revents : SHORT; + end; + {$NODEFINE TWSAPOLLFD} + TWSAPOLLFD = WSAPOLLFD; + {$EXTERNALSYM PWSAPOLLFD} + PWSAPOLLFD = ^TWSAPOLLFD; + {$EXTERNALSYM LPWSAPOLLFD} + LPWSAPOLLFD = PWSAPOLLFD; +{$ENDIF} + +{ WinSock 2 extensions -- data types for the condition function in } +{ WSAAccept() and overlapped I/O completion routine. } +type + {$EXTERNALSYM LPCONDITIONPROC} + LPCONDITIONPROC = function(lpCallerId: LPWSABUF; lpCallerData: LPWSABUF; lpSQOS, pGQOS: LPQOS; + lpCalleeId,lpCalleeData: LPWSABUF; g: GROUP; dwCallbackData: DWORD): Integer; stdcall; + {$EXTERNALSYM LPWSAOVERLAPPED_COMPLETION_ROUTINE} + LPWSAOVERLAPPED_COMPLETION_ROUTINE = procedure(const dwError, cbTransferred: DWORD; + const lpOverlapped : LPWSAOVERLAPPED; const dwFlags: DWORD); stdcall; + + {$EXTERNALSYM WSACOMPLETIONTYPE} + {$EXTERNALSYM NSP_NOTIFY_IMMEDIATELY} + {$EXTERNALSYM NSP_NOTIFY_HWND} + {$EXTERNALSYM NSP_NOTIFY_EVENT} + {$EXTERNALSYM NSP_NOTIFY_PORT} + {$EXTERNALSYM NSP_NOTIFY_APC} + WSACOMPLETIONTYPE = (NSP_NOTIFY_IMMEDIATELY, + NSP_NOTIFY_HWND, + NSP_NOTIFY_EVENT, + NSP_NOTIFY_PORT, + NSP_NOTIFY_APC); + {$EXTERNALSYM WSACOMPLETION_WINDOWMESSAGE} + WSACOMPLETION_WINDOWMESSAGE = record + hWnd : HWND; + uMsg : UINT; + context : WPARAM; + end; + {$EXTERNALSYM WSACOMPLETION_EVENT} + WSACOMPLETION_EVENT = record + lpOverlapped : LPWSAOVERLAPPED; + end; + {$EXTERNALSYM WSACOMPLETION_APC} + WSACOMPLETION_APC = record + lpOverlapped : LPWSAOVERLAPPED; + lpfnCompletionProc : LPWSAOVERLAPPED_COMPLETION_ROUTINE; + end; + {$EXTERNALSYM WSACOMPLETION_PORT} + WSACOMPLETION_PORT = record + lpOverlapped : LPWSAOVERLAPPED; + hPort : THANDLE; + Key : ULONG_PTR; + end; + {$EXTERNALSYM WSACOMPLETION_UNION} + WSACOMPLETION_union = record + case Integer of + 0: (WindowMessage : WSACOMPLETION_WINDOWMESSAGE); + 1: (Event : WSACOMPLETION_EVENT); + 2: (Apc : WSACOMPLETION_APC); + 3: (Port : WSACOMPLETION_PORT); + end; + {$EXTERNALSYM WSACOMPLETION} + WSACOMPLETION = record + _Type : WSACOMPLETIONTYPE; + Parameters : WSACOMPLETION_union; + end; + {$EXTERNALSYM PWSACOMPLETION} + PWSACOMPLETION = ^WSACOMPLETION; + {$EXTERNALSYM LPWSACOMPLETION} + LPWSACOMPLETION = PWSACOMPLETION; + +type +{$IFDEF INCL_WINSOCK_API_TYPEDEFS} + {$EXTERNALSYM LPFN_WSASTARTUP} + LPFN_WSASTARTUP = function(const wVersionRequired: WORD; out WSData: TWSAData): Integer; stdcall; + {$EXTERNALSYM LPFN_WSACLEANUP} + LPFN_WSACLEANUP = function: Integer; stdcall; + {$EXTERNALSYM LPFN_ACCEPT} + LPFN_ACCEPT = function(const s: TSocket; AAddr: PSOCKADDR; addrlen: PInteger): TSocket; stdcall; + {$EXTERNALSYM LPFN_BIND} + LPFN_BIND = function(const s: TSocket; const name: PSOCKADDR; const namelen: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_CLOSESOCKET} + LPFN_CLOSESOCKET = function(const s: TSocket): Integer; stdcall; + {$EXTERNALSYM LPFN_CONNECT} + LPFN_CONNECT = function(const s: TSocket; const name: PSOCKADDR; const namelen: Integer): Integer; stdcall; + {$EXTERNALSYM lpfn_IOCTLSOCKET} + LPFN_IOCTLSOCKET = function(const s: TSocket; const cmd: DWORD; var arg: u_long): Integer; stdcall; + {$EXTERNALSYM LPFN_GETPEERNAME} + LPFN_GETPEERNAME = function(const s: TSocket; const name: PSOCKADDR; var namelen: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_GETSOCKNAME} + LPFN_GETSOCKNAME = function(const s: TSocket; const name: PSOCKADDR; var namelen: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_GETSOCKOPT} + LPFN_GETSOCKOPT = function(const s: TSocket; const level, optname: Integer; optval: PAnsiChar; var optlen: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_HTONL} + LPFN_HTONL = function(hostlong: u_long): u_long; stdcall; + {$EXTERNALSYM LPFN_HTONS} + LPFN_HTONS = function(hostshort: u_short): u_short; stdcall; + {$EXTERNALSYM LPFN_INET_ADDR} + LPFN_INET_ADDR = function(cp: PAnsiChar): u_long; stdcall; + {$EXTERNALSYM LPFN_INET_NTOA} + LPFN_INET_NTOA = function(inaddr: TInAddr): PAnsiChar; stdcall; + {$EXTERNALSYM LPFN_LISTEN} + LPFN_LISTEN = function(const s: TSocket; backlog: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_NTOHL} + LPFN_NTOHL = function(netlong: u_long): u_long; stdcall; + {$EXTERNALSYM LPFN_NTOHS} + LPFN_NTOHS = function(netshort: u_short): u_short; stdcall; + {$EXTERNALSYM LPFN_RECV} + LPFN_RECV = function(const s: TSocket; var Buf; len, flags: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_RECVFROM} + LPFN_RECVFROM = function(const s: TSocket; var Buf; len, flags: Integer; from: PSOCKADDR; fromlen: PInteger): Integer; stdcall; + {$EXTERNALSYM LPFN_SELECT} + LPFN_SELECT = function(nfds: Integer; readfds, writefds, exceptfds: PFDSet; timeout: PTimeVal): Integer; stdcall; + {$EXTERNALSYM LPFN_SEND} + LPFN_SEND = function(const s: TSocket; const Buf; len, flags: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_SENDTO} + LPFN_SENDTO = function(const s: TSocket; const Buf; const len, flags: Integer; const addrto: PSOCKADDR; const tolen: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_SETSOCKOPT} + LPFN_SETSOCKOPT = function(const s: TSocket; const level, optname: Integer; optval: PAnsiChar; const optlen: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_SHUTDOWN} + LPFN_SHUTDOWN = function(const s: TSocket; const how: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_SOCKET} + LPFN_SOCKET = function(const af, istruct, protocol: Integer): TSocket; stdcall; + {$EXTERNALSYM LPFN_GETHOSTBYADDR} + LPFN_GETHOSTBYADDR = function(AAddr: Pointer; const len, addrtype: Integer): PHostEnt; stdcall; + {$EXTERNALSYM LPFN_GETHOSTBYNAME} + LPFN_GETHOSTBYNAME = function(name: PAnsiChar): PHostEnt; stdcall; + {$EXTERNALSYM LPFN_GETHOSTNAME} + LPFN_GETHOSTNAME = function(name: PAnsiChar; len: Integer): Integer; stdcall; +{$IFDEF WINCE} + // WinCE specific for setting the host name + {$EXTERNALSYM LPFN_SETHOSTNAME} + LPFN_SETHOSTNAME = function(pName : PAnsiChar; len : Integer) : Integer; stdcall; +{$ENDIF} + {$EXTERNALSYM LPFN_GETSERVBYPORT} + LPFN_GETSERVBYPORT = function(const port: Integer; const proto: PAnsiChar): PServEnt; stdcall; + {$EXTERNALSYM LPFN_GETSERVBYNAME} + LPFN_GETSERVBYNAME = function(const name, proto: PAnsiChar): PServEnt; stdcall; + {$EXTERNALSYM LPFN_GETPROTOBYNUMBER} + LPFN_GETPROTOBYNUMBER = function(const proto: Integer): PProtoEnt; stdcall; + {$EXTERNALSYM LPFN_GETPROTOBYNAME} + LPFN_GETPROTOBYNAME = function(const name: PAnsiChar): PProtoEnt; stdcall; + {$EXTERNALSYM LPFN_WSASETLASTERROR} + LPFN_WSASETLASTERROR = procedure(const iError: Integer); stdcall; + {$EXTERNALSYM LPFN_WSAGETLASTERROR} + LPFN_WSAGETLASTERROR = function: Integer; stdcall; +{$IFNDEF WINCE} + {$EXTERNALSYM LPFN_WSACANCELASYNCREQUEST} + LPFN_WSACANCELASYNCREQUEST = function(hAsyncTaskHandle: THandle): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAISBLOCKING} + LPFN_WSAISBLOCKING = function: BOOL; stdcall; + {$EXTERNALSYM LPFN_WSAUNHOOKBLOCKINGHOOK} + LPFN_WSAUNHOOKBLOCKINGHOOK = function: Integer; stdcall; + {$EXTERNALSYM LPFN_WSASETBLOCKINGHOOK} + LPFN_WSASETBLOCKINGHOOK = function(lpBlockFunc: TFarProc): TFarProc; stdcall; + {$EXTERNALSYM LPFN_WSACANCELBLOCKINGCALL} + LPFN_WSACANCELBLOCKINGCALL = function: Integer; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCGETSERVBYNAME} + LPFN_WSAASYNCGETSERVBYNAME = function(HWindow: HWND; wMsg: u_int; name, proto, buf: PAnsiChar; buflen: Integer): THandle; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCGETSERVBYPORT} + LPFN_WSAASYNCGETSERVBYPORT = function(HWindow: HWND; wMsg, port: u_int; proto, buf: PAnsiChar; buflen: Integer): THandle; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCGETPROTOBYNAME} + LPFN_WSAASYNCGETPROTOBYNAME = function(HWindow: HWND; wMsg: u_int; name, buf: PAnsiChar; buflen: Integer): THandle; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCGETPROTOBYNUMBER} + LPFN_WSAASYNCGETPROTOBYNUMBER = function(HWindow: HWND; wMsg: u_int; number: Integer; buf: PAnsiChar; buflen: Integer): THandle; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCGETHOSTBYNAME} + LPFN_WSAASYNCGETHOSTBYNAME = function(HWindow: HWND; wMsg: u_int; name, buf: PAnsiChar; buflen: Integer): THandle; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCGETHOSTBYADDR} + LPFN_WSAASYNCGETHOSTBYADDR = function(HWindow: HWND; wMsg: u_int; AAddr: PAnsiChar; len, istruct: Integer; buf: PAnsiChar; buflen: Integer): THandle; stdcall; + {$EXTERNALSYM LPFN_WSAASYNCSELECT} + LPFN_WSAASYNCSELECT = function(const s: TSocket; HWindow: HWND; wMsg: u_int; lEvent: Longint): Integer; stdcall; +{$ENDIF} + {$EXTERNALSYM LPFN___WSAFDISSET} + LPFN___WSAFDISSET = function(const s: TSocket; var FDSet: TFDSet): Bool; stdcall; + +// WinSock 2 API new function prototypes + {$EXTERNALSYM LPFN_WSAACCEPT} + LPFN_WSAACCEPT = function(const s : TSocket; AAddr : PSOCKADDR; addrlen : PInteger; lpfnCondition : LPCONDITIONPROC; const dwCallbackData : DWORD): TSocket; stdcall; + {$EXTERNALSYM LPFN_WSAENUMPROTOCOLSA} + LPFN_WSAENUMPROTOCOLSA = function(lpiProtocols : PInteger; lpProtocolBuffer : LPWSAPROTOCOL_INFOA; var lpdwBufferLength : DWORD) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAENUMPROTOCOLSW} + LPFN_WSAENUMPROTOCOLSW = function(lpiProtocols : PInteger; lpProtocolBuffer : LPWSAPROTOCOL_INFOW; var lpdwBufferLength : DWORD) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAGETOVERLAPPEDRESULT} + LPFN_WSAGETOVERLAPPEDRESULT = function(const s : TSocket; AOverlapped: Pointer; lpcbTransfer : LPDWORD; fWait : BOOL; var lpdwFlags : DWORD) : WordBool; stdcall; + {$EXTERNALSYM LPFN_WSAIOCTL} + LPFN_WSAIOCTL = function(const s : TSocket; dwIoControlCode : DWORD; lpvInBuffer : Pointer; cbInBuffer : DWORD; lpvOutBuffer : Pointer; cbOutBuffer : DWORD; + lpcbBytesReturned : LPDWORD; AOverlapped: Pointer; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) : LongInt; stdcall; + {$EXTERNALSYM LPFN_WSARECVFROM} + LPFN_WSARECVFROM = function(const s : TSocket; lpBuffers : LPWSABUF; dwBufferCount : DWORD; var lpNumberOfBytesRecvd : DWORD; var lpFlags : DWORD; + lpFrom : PSOCKADDR; lpFromlen : PInteger; AOverlapped: Pointer; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; + + {$EXTERNALSYM LPFN_TRANSMITFILE} + LPFN_TRANSMITFILE = function(hSocket: TSocket; hFile: THandle; nNumberOfBytesToWrite, nNumberOfBytesPerSend: DWORD; + lpOverlapped: POverlapped; lpTransmitBuffers: LPTRANSMIT_FILE_BUFFERS; dwReserved: DWORD): BOOL; stdcall; + {$EXTERNALSYM LPFN_ACCEPTEX} + LPFN_ACCEPTEX = function(sListenSocket, sAcceptSocket: TSocket; + lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength, + dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD; + lpOverlapped: POverlapped): BOOL; stdcall; + {$IFNDEF WINCE} + {$EXTERNALSYM LPFN_WSACONNECTBYLIST} + LPFN_WSACONNECTBYLIST = function(const s : TSocket; SocketAddressList : PSOCKET_ADDRESS_LIST; + var LocalAddressLength : DWORD; LocalAddress : LPSOCKADDR; + var RemoteAddressLength : DWORD; RemoteAddress : LPSOCKADDR; + timeout : Ptimeval; Reserved : LPWSAOVERLAPPED):LongBool; stdcall; + {$EXTERNALSYM LPFN_WSACONNECTBYNAMEA} + LPFN_WSACONNECTBYNAMEA = function(const s : TSOCKET; + nodename : PAnsiChar; servicename : PAnsiChar; + var LocalAddressLength : DWORD; LocalAddress : LPSOCKADDR; + var RemoteAddressLength : DWORD; RemoteAddress : LPSOCKADDR; + timeout : Ptimeval; Reserved : LPWSAOVERLAPPED) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSACONNECTBYNAMEW} + LPFN_WSACONNECTBYNAMEW = function(const s : TSOCKET; + nodename : PWChar; servicename : PWChar; + var LocalAddressLength : DWORD; LocalAddress : LPSOCKADDR; + var RemoteAddressLength : DWORD; RemoteAddress : LPSOCKADDR; + timeout : Ptimeval; Reserved : LPWSAOVERLAPPED) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSACONNECTBYNAME} + {$IFDEF UNICODE} + LPFN_WSACONNECTBYNAME = LPFN_WSACONNECTBYNAMEW; + {$ELSE} + LPFN_WSACONNECTBYNAME = LPFN_WSACONNECTBYNAMEA; + {$ENDIF} +{$ENDIF} + + {$EXTERNALSYM LPFN_WSAENUMPROTOCOLS} + {wince} + {$IFDEF UNICODE} + LPFN_WSAENUMPROTOCOLS = LPFN_WSAENUMPROTOCOLSW; + {$ELSE} + LPFN_WSAENUMPROTOCOLS = LPFN_WSAENUMPROTOCOLSA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSACLOSEEVENT} + LPFN_WSACLOSEEVENT = function(const hEvent : WSAEVENT) : WordBool; stdcall; + {$EXTERNALSYM LPFN_WSACONNECT} + LPFN_WSACONNECT = function(const s : TSocket; const name : PSOCKADDR; const namelen : Integer; lpCallerData, lpCalleeData : LPWSABUF; lpSQOS, lpGQOS : LPQOS) : Integer; stdcall; + + {$EXTERNALSYM LPFN_WSACREATEEVENT} + LPFN_WSACREATEEVENT = function: WSAEVENT; stdcall; + + {$IFNDEF WINCE} + {$EXTERNALSYM LPFN_WSADUPLICATESOCKETA} + LPFN_WSADUPLICATESOCKETA = function(const s : TSocket; const dwProcessId : DWORD; lpProtocolInfo : LPWSAPROTOCOL_INFOA) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSADUPLICATESOCKETW} + LPFN_WSADUPLICATESOCKETW = function(const s : TSocket; const dwProcessId : DWORD; lpProtocolInfo : LPWSAPROTOCOL_INFOW) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSADUPLICATESOCKET} + {$IFDEF UNICODE} + LPFN_WSADUPLICATESOCKET = LPFN_WSADUPLICATESOCKETW; + {$ELSE} + LPFN_WSADUPLICATESOCKET = LPFN_WSADUPLICATESOCKETA; + {$ENDIF} + {$ENDIF} + + {$EXTERNALSYM LPFN_WSAENUMNETWORKEVENTS} + LPFN_WSAENUMNETWORKEVENTS = function(const s : TSocket; const hEventObject : WSAEVENT; lpNetworkEvents : LPWSANETWORKEVENTS) :Integer; stdcall; + + {$EXTERNALSYM LPFN_WSAEVENTSELECT} + LPFN_WSAEVENTSELECT = function(const s : TSocket; const hEventObject : WSAEVENT; lNetworkEvents : LongInt): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAGETQOSBYNAME} + LPFN_WSAGETQOSBYNAME = function(const s : TSocket; lpQOSName : LPWSABUF; lpQOS : LPQOS): WordBool; stdcall; + {$EXTERNALSYM LPFN_WSAHTONL} + LPFN_WSAHTONL = function(const s : TSocket; hostlong : u_long; var lpnetlong : DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAHTONS} + LPFN_WSAHTONS = function(const s : TSocket; hostshort : u_short; var lpnetshort : WORD): Integer; stdcall; + + {$EXTERNALSYM LPFN_WSAJOINLEAF} + LPFN_WSAJOINLEAF = function(const s : TSocket; name : PSOCKADDR; namelen : Integer; lpCallerData, lpCalleeData : LPWSABUF; + lpSQOS,lpGQOS : LPQOS; dwFlags : DWORD) : TSocket; stdcall; + + {$EXTERNALSYM LPFN_WSANTOHL} + LPFN_WSANTOHL = function(const s : TSocket; netlong : u_long; var lphostlong : DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSANTOHS} + LPFN_WSANTOHS = function(const s : TSocket; netshort : u_short; var lphostshort : WORD): Integer; stdcall; + + {$EXTERNALSYM LPFN_WSARECV} + LPFN_WSARECV = function(const s : TSocket; lpBuffers : LPWSABUF; dwBufferCount : DWORD; var lpNumberOfBytesRecvd : DWORD; var lpFlags : DWORD; + lpOverlapped : LPWSAOVERLAPPED; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; + {$EXTERNALSYM LPFN_WSARECVDISCONNECT} + LPFN_WSARECVDISCONNECT = function(const s : TSocket; lpInboundDisconnectData : LPWSABUF): Integer; stdcall; + + {$EXTERNALSYM LPFN_WSARESETEVENT} + LPFN_WSARESETEVENT = function(hEvent : WSAEVENT): WordBool; stdcall; + + {$EXTERNALSYM LPFN_WSASEND} + LPFN_WSASEND = function(const s : TSocket; lpBuffers : LPWSABUF; dwBufferCount : DWORD; var lpNumberOfBytesSent : DWORD; dwFlags : DWORD; + lpOverlapped : LPWSAOVERLAPPED; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASENDDISCONNECT} + LPFN_WSASENDDISCONNECT = function(const s : TSocket; lpOutboundDisconnectData : LPWSABUF): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASENDTO} + LPFN_WSASENDTO = function(const s : TSocket; lpBuffers : LPWSABUF; dwBufferCount : DWORD; var lpNumberOfBytesSent : DWORD; dwFlags : DWORD; + lpTo : LPSOCKADDR; iTolen : Integer; lpOverlapped : LPWSAOVERLAPPED; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; + + {$EXTERNALSYM LPFN_WSASETEVENT} + LPFN_WSASETEVENT = function(hEvent : WSAEVENT): WordBool; stdcall; + + {$EXTERNALSYM LPFN_WSASOCKETA} + LPFN_WSASOCKETA = function(af, iType, protocol : Integer; lpProtocolInfo : LPWSAPROTOCOL_INFOA; g : GROUP; dwFlags : DWORD): TSocket; stdcall; + {$EXTERNALSYM LPFN_WSASOCKETW} + LPFN_WSASOCKETW = function(af, iType, protocol : Integer; lpProtocolInfo : LPWSAPROTOCOL_INFOW; g : GROUP; dwFlags : DWORD): TSocket; stdcall; + {$EXTERNALSYM LPFN_WSASOCKET} + {$IFDEF UNICODE} + LPFN_WSASOCKET = LPFN_WSASOCKETW; + {$ELSE} + LPFN_WSASOCKET = LPFN_WSASOCKETA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSAWAITFORMULTIPLEEVENTS} + LPFN_WSAWAITFORMULTIPLEEVENTS = function(cEvents : DWORD; lphEvents : PWSAEVENT; fWaitAll : LongBool; + dwTimeout : DWORD; fAlertable : LongBool): DWORD; stdcall; + + {$EXTERNALSYM LPFN_WSAADDRESSTOSTRINGA} + LPFN_WSAADDRESSTOSTRINGA = function(lpsaAddress : PSOCKADDR; const dwAddressLength : DWORD; const lpProtocolInfo : LPWSAPROTOCOL_INFOA; + const lpszAddressString : PAnsiChar; var lpdwAddressStringLength : DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAADDRESSTOSTRINGW} + LPFN_WSAADDRESSTOSTRINGW = function(lpsaAddress : PSOCKADDR; const dwAddressLength : DWORD; const lpProtocolInfo : LPWSAPROTOCOL_INFOW; + const lpszAddressString : PWideChar; var lpdwAddressStringLength : DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAADDRESSTOSTRING} + {$IFDEF UNICODE} + LPFN_WSAADDRESSTOSTRING = LPFN_WSAADDRESSTOSTRINGW; + {$ELSE} + LPFN_WSAADDRESSTOSTRING = LPFN_WSAADDRESSTOSTRINGA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSASTRINGTOADDRESSA} + LPFN_WSASTRINGTOADDRESSA = function(const AddressString : PAnsiChar; const AddressFamily: Integer; const lpProtocolInfo : LPWSAPROTOCOL_INFOA; + var lpAddress : TSockAddr; var lpAddressLength : Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASTRINGTOADDRESSW} + LPFN_WSASTRINGTOADDRESSW = function(const AddressString : PWideChar; const AddressFamily: Integer; const lpProtocolInfo : LPWSAPROTOCOL_INFOW; + var lpAddress : TSockAddr; var lpAddressLength : Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASTRINGTOADDRESS} + {$IFDEF UNICODE} + LPFN_WSASTRINGTOADDRESS = LPFN_WSASTRINGTOADDRESSW; + {$ELSE} + LPFN_WSASTRINGTOADDRESS = LPFN_WSASTRINGTOADDRESSA; + {$ENDIF} + +// Registration and Name Resolution API functions + {$EXTERNALSYM LPFN_WSALOOKUPSERVICEBEGINA} + LPFN_WSALOOKUPSERVICEBEGINA = function(var qsRestrictions : TWSAQuerySetA; const dwControlFlags : DWORD; var hLookup : THandle): Integer; stdcall; + {$EXTERNALSYM LPFN_WSALOOKUPSERVICEBEGINw} + LPFN_WSALOOKUPSERVICEBEGINW = function(var qsRestrictions : TWSAQuerySetW; const dwControlFlags : DWORD; var hLookup : THandle): Integer; stdcall; + {$EXTERNALSYM LPFN_WSALOOKUPSERVICEBEGIN} + {$IFDEF UNICODE} + LPFN_WSALOOKUPSERVICEBEGIN = LPFN_WSALOOKUPSERVICEBEGINW; + {$ELSE} + LPFN_WSALOOKUPSERVICEBEGIN = LPFN_WSALOOKUPSERVICEBEGINA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSALOOKUPSERVICENEXTA} + LPFN_WSALOOKUPSERVICENEXTA = function(const hLookup : THandle; const dwControlFlags : DWORD; var dwBufferLength : DWORD; lpqsResults : PWSAQUERYSETA): Integer; stdcall; + {$EXTERNALSYM LPFN_WSALOOKUPSERVICENEXTW} + LPFN_WSALOOKUPSERVICENEXTW = function(const hLookup : THandle; const dwControlFlags : DWORD; var dwBufferLength : DWORD; lpqsResults : PWSAQUERYSETW): Integer; stdcall; + {$EXTERNALSYM LPFN_WSALOOKUPSERVICENEXT} + {$IFDEF UNICODE} + LPFN_WSALOOKUPSERVICENEXT = LPFN_WSALOOKUPSERVICENEXTW; + {$ELSE} + LPFN_WSALOOKUPSERVICENEXT = LPFN_WSALOOKUPSERVICENEXTA; + {$ENDIF} + + //WinCE 4.20 doesn't support WSANSPIoctl but later versions do. + {$EXTERNALSYM LPFN_WSANSPIOCTL} + LPFN_WSANSPIOCTL = function(const hLookup : THANDLE; const dwControlCode : DWORD; lpvInBuffer : Pointer; var cbInBuffer : DWORD; lpvOutBuffer : Pointer; var cbOutBuffer : DWORD; var lpcbBytesReturned : DWORD; lpCompletion : LPWSACOMPLETION) : Integer; stdcall; + + {$EXTERNALSYM LPFN_WSALOOKUPSERVICEEND} + LPFN_WSALOOKUPSERVICEEND = function(const hLookup : THandle): Integer; stdcall; + + + {$EXTERNALSYM LPFN_WSAINSTALLSERVICECLASSA} + LPFN_WSAINSTALLSERVICECLASSA = function(const lpServiceClassInfo : LPWSASERVICECLASSINFOA) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAINSTALLSERVICECLASSW} + LPFN_WSAINSTALLSERVICECLASSW = function(const lpServiceClassInfo : LPWSASERVICECLASSINFOW) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAINSTALLSERVICECLASS} + {$IFDEF UNICODE} + LPFN_WSAINSTALLSERVICECLASS = LPFN_WSAINSTALLSERVICECLASSW; + {$ELSE} + LPFN_WSAINSTALLSERVICECLASS = LPFN_WSAINSTALLSERVICECLASSA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSAREMOVESERVICECLASS} + LPFN_WSAREMOVESERVICECLASS = function(const lpServiceClassId : LPGUID) : Integer; stdcall; + + {$EXTERNALSYM LPFN_WSAGETSERVICECLASSINFOA} + LPFN_WSAGETSERVICECLASSINFOA = function(const lpProviderId : LPGUID; const lpServiceClassId : LPGUID; var lpdwBufSize : DWORD; + lpServiceClassInfo : LPWSASERVICECLASSINFOA): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAGETSERVICECLASSINFOW} + LPFN_WSAGETSERVICECLASSINFOW = function(const lpProviderId : LPGUID; const lpServiceClassId : LPGUID; var lpdwBufSize : DWORD; + lpServiceClassInfo : LPWSASERVICECLASSINFOW): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAGETSERVICECLASSINFO} + {$IFDEF UNICODE} + LPFN_WSAGETSERVICECLASSINFO = LPFN_WSAGETSERVICECLASSINFOW; + {$ELSE} + LPFN_WSAGETSERVICECLASSINFO = LPFN_WSAGETSERVICECLASSINFOA; + {$ENDIF} + {$ENDIF} + + {$EXTERNALSYM LPFN_WSAENUMNAMESPACEPROVIDERSA} + LPFN_WSAENUMNAMESPACEPROVIDERSA = function(var lpdwBufferLength: DWORD; const lpnspBuffer: LPWSANAMESPACE_INFOA): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAENUMNAMESPACEPROVIDERSW} + LPFN_WSAENUMNAMESPACEPROVIDERSW = function(var lpdwBufferLength: DWORD; const lpnspBuffer: LPWSANAMESPACE_INFOW): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAENUMNAMESPACEPROVIDERS} + {$IFDEF UNICODE} + LPFN_WSAENUMNAMESPACEPROVIDERS = LPFN_WSAENUMNAMESPACEPROVIDERSW; + {$ELSE} + LPFN_WSAENUMNAMESPACEPROVIDERS = LPFN_WSAENUMNAMESPACEPROVIDERSA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDA} + LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDA = function(const lpServiceClassId: LPGUID; lpszServiceClassName: PAnsiChar; var lpdwBufferLength: DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDW} + LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDW = function(const lpServiceClassId: LPGUID; lpszServiceClassName: PWideChar; var lpdwBufferLength: DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSAGETSERVICECLASSNAMEBYCLASSID} + {$IFDEF UNICODE} + LPFN_WSAGETSERVICECLASSNAMEBYCLASSID = LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDW; + {$ELSE} + LPFN_WSAGETSERVICECLASSNAMEBYCLASSID = LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSASETSERVICEA} + LPFN_WSASETSERVICEA = function(const lpqsRegInfo: LPWSAQUERYSETA; const essoperation: WSAESETSERVICEOP; const dwControlFlags: DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASETSERVICEW} + LPFN_WSASETSERVICEW = function(const lpqsRegInfo: LPWSAQUERYSETW; const essoperation: WSAESETSERVICEOP; const dwControlFlags: DWORD): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASETSERVICE} + {$IFDEF UNICODE} + LPFN_WSASETSERVICE = LPFN_WSASETSERVICEW; + {$ELSE} + LPFN_WSASETSERVICE = LPFN_WSASETSERVICEA; + {$ENDIF} + + {$EXTERNALSYM LPFN_WSAPROVIDERCONFIGCHANGE} + LPFN_WSAPROVIDERCONFIGCHANGE = function(var lpNotificationHandle : THandle; lpOverlapped : LPWSAOVERLAPPED; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) : Integer; stdcall; + + //microsoft specific extension + {$EXTERNALSYM LPFN_GETACCEPTEXSOCKADDRS} + LPFN_GETACCEPTEXSOCKADDRS = procedure(lpOutputBuffer: Pointer; + dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD; + var LocalSockaddr: PSockAddr; var LocalSockaddrLength: Integer; + var RemoteSockaddr: PSockAddr; var RemoteSockaddrLength: Integer); stdcall; + + {$IFNDEF WINCE} + //This is defined in the Winapi.Windows Mobile 6 Standard SDK Refresh + //but I'm not sure what .DLL the function is in. I also couldn't find a WSAID + //constant for it. + {$EXTERNALSYM LPFN_WSARECVEX} + LPFN_WSARECVEX = function(s: TSocket; var buf; len: Integer; var flags: Integer): Integer; stdcall; + {$ENDIF} + + //Winapi.Windows Server 2003, Winapi.Windows Vista + {$EXTERNALSYM LPFN_CONNECTEX} + LPFN_CONNECTEX = function(const s : TSocket; const name: PSOCKADDR; const namelen: Integer; lpSendBuffer : Pointer; dwSendDataLength : DWORD; var lpdwBytesSent : DWORD; lpOverlapped : LPWSAOVERLAPPED) : BOOL; stdcall; + {$EXTERNALSYM LPFN_DISCONNECTEX} + LPFN_DISCONNECTEX = function(const hSocket : TSocket; AOverlapped: Pointer; const dwFlags : DWORD; const dwReserved : DWORD) : BOOL; stdcall; + {$EXTERNALSYM LPFN_WSARECVMSG} //XP and Server 2003 only + LPFN_WSARECVMSG = function(const s : TSocket; lpMsg : LPWSAMSG; var lpNumberOfBytesRecvd : DWORD; AOverlapped: Pointer; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; + {$EXTERNALSYM LPFN_TRANSMITPACKETS} + LPFN_TRANSMITPACKETS = function(s: TSocket; lpPacketArray: LPTRANSMIT_PACKETS_ELEMENT; nElementCount: DWORD; nSendSize: DWORD; lpOverlapped: LPWSAOVERLAPPED; dwFlags: DWORD): BOOL; stdcall; + //Winapi.Windows Vista, Winapi.Windows Server 2008 +{$IFNDEF WINCE} + {$EXTERNALSYM LPFN_WSASENDMSG} + LPFN_WSASENDMSG = function(const s : TSocket; lpMsg : LPWSAMSG; const dwFlags : DWORD; var lpNumberOfBytesSent : DWORD; lpOverlapped : LPWSAOVERLAPPED; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAPOLL} + LPFN_WSAPOLL = function(fdarray : LPWSAPOLLFD; const nfds : u_long; const timeout : Integer) : Integer; stdcall; +{$ENDIF} // $IFDEF INCL_WINSOCK_API_TYPEDEFS + + +const + //GUID's for Microsoft extensions + {$EXTERNALSYM WSAID_ACCEPTEX} + WSAID_ACCEPTEX: TGuid = (D1:$b5367df1;D2:$cbac;D3:$11cf;D4:($95,$ca,$00,$80,$5f,$48,$a1,$92)); + {$EXTERNALSYM WSAID_CONNECTEX} + WSAID_CONNECTEX: TGuid = (D1:$25a207b9;D2:$ddf3;D3:$4660;D4:($8e,$e9,$76,$e5,$8c,$74,$06,$3e)); + {$EXTERNALSYM WSAID_DISCONNECTEX} + WSAID_DISCONNECTEX: TGuid = (D1:$7fda2e11;D2:$8630;D3:$436f;D4:($a0,$31,$f5,$36,$a6,$ee,$c1,$57)); + {$EXTERNALSYM WSAID_GETACCEPTEXSOCKADDRS} + WSAID_GETACCEPTEXSOCKADDRS: TGuid = (D1:$b5367df2;D2:$cbac;D3:$11cf;D4:($95,$ca,$00,$80,$5f,$48,$a1,$92)); + {$EXTERNALSYM WSAID_TRANSMITFILE} + WSAID_TRANSMITFILE: TGuid = (D1:$b5367df0; D2:$cbac; D3:$11cf; D4:($95, $ca, $00, $80, $5f, $48, $a1, $92)); + {$EXTERNALSYM WSAID_TRANSMITPACKETS} + WSAID_TRANSMITPACKETS: TGuid = (D1:$d9689da0;D2:$1f90;D3:$11d3;D4:($99,$71,$00,$c0,$4f,$68,$c8,$76)); +{$IFNDEF WINCE} + {$EXTERNALSYM WSAID_WSAPOLL} + WSAID_WSAPOLL: TGuid = (D1:$18C76F85;D2:$DC66;D3:$4964;D4:($97,$2E,$23,$C2,$72,$38,$31,$2B)); +{$ENDIF} + {$EXTERNALSYM WSAID_WSARECVMSG} + WSAID_WSARECVMSG: TGuid = (D1:$f689d7c8;D2:$6f1f;D3:$436b;D4:($8a,$53,$e5,$4f,$e3,$51,$c3,$22)); +{$IFNDEF WINCE} + {$EXTERNALSYM WSAID_WSASENDMSG} + WSAID_WSASENDMSG : TGuid = (D1:$a441e712;D2:$754f;D3:$43ca;D4:($84,$a7,$0d,$ee,$44,$cf,$60,$6d)); +{$ENDIF} + +{$IFDEF WS2_DLL_FUNC_VARS} +var + {$EXTERNALSYM WSAStartup} + WSAStartup : LPFN_WSASTARTUP = nil; + {$EXTERNALSYM WSACleanup} + WSACleanup : LPFN_WSACLEANUP = nil; + {$EXTERNALSYM accept} + accept : LPFN_ACCEPT = nil; + {$EXTERNALSYM bind} + bind : LPFN_BIND = nil; + {$EXTERNALSYM closesocket} + closesocket : LPFN_CLOSESOCKET = nil; + {$EXTERNALSYM connect} + connect : LPFN_CONNECT = nil; + {$EXTERNALSYM ioctlsocket} + ioctlsocket : LPFN_IOCTLSOCKET = nil; + {$EXTERNALSYM getpeername} + getpeername : LPFN_GETPEERNAME = nil; + {$EXTERNALSYM getsockname} + getsockname : LPFN_GETSOCKNAME = nil; + {$EXTERNALSYM getsockopt} + getsockopt : LPFN_GETSOCKOPT = nil; + {$EXTERNALSYM htonl} + htonl : LPFN_HTONL = nil; + {$EXTERNALSYM htons} + htons : LPFN_HTONS = nil; + {$EXTERNALSYM inet_addr} + inet_addr : LPFN_INET_ADDR = nil; + {$EXTERNALSYM inet_ntoa} + inet_ntoa : LPFN_INET_NTOA = nil; + {$EXTERNALSYM listen} + listen : LPFN_LISTEN = nil; + {$EXTERNALSYM ntohl} + ntohl : LPFN_NTOHL = nil; + {$EXTERNALSYM ntohs} + ntohs : LPFN_NTOHS = nil; + {$EXTERNALSYM recv} + recv : LPFN_RECV = nil; + {$EXTERNALSYM recvfrom} + recvfrom : LPFN_RECVFROM = nil; + {$EXTERNALSYM select} + select : LPFN_SELECT = nil; + {$EXTERNALSYM send} + send : LPFN_SEND = nil; + {$EXTERNALSYM sendto} + sendto : LPFN_SENDTO = nil; + {$EXTERNALSYM setsockopt} + setsockopt : LPFN_SETSOCKOPT = nil; + {$EXTERNALSYM shutdown} + shutdown : LPFN_SHUTDOWN = nil; + {$EXTERNALSYM socket} + socket : LPFN_SOCKET = nil; + {$EXTERNALSYM gethostbyaddr} + gethostbyaddr : LPFN_GETHOSTBYADDR = nil; + {$EXTERNALSYM gethostbyname} + gethostbyname : LPFN_GETHOSTBYNAME = nil; + {$EXTERNALSYM gethostname} + gethostname : LPFN_GETHOSTNAME = nil; + {$IFDEF WINCE} + {$EXTERNALSYM sethostname} + sethostname : LPFN_SETHOSTNAME = nil; + {$ENDIF} + {$EXTERNALSYM getservbyport} + getservbyport : LPFN_GETSERVBYPORT = nil; + {$EXTERNALSYM getservbyname} + getservbyname : LPFN_GETSERVBYNAME = nil; + {$EXTERNALSYM getprotobynumber} + getprotobynumber : LPFN_GETPROTOBYNUMBER = nil; + {$EXTERNALSYM getprotobyname} + getprotobyname : LPFN_GETPROTOBYNAME = nil; + {$EXTERNALSYM WSASetLastError} + WSASetLastError : LPFN_WSASETLASTERROR = nil; + {$EXTERNALSYM WSAGetLastError} + WSAGetLastError : LPFN_WSAGETLASTERROR = nil; +{$IFNDEF WINCE} + {$EXTERNALSYM WSAIsblocking} + WSAIsBlocking : LPFN_WSAISBLOCKING = nil; + {$EXTERNALSYM WSAUnhookBlockingHook} + WSAUnhookBlockingHook : LPFN_WSAUNHOOKBLOCKINGHOOK = nil; + {$EXTERNALSYM WSASetBlockingHook} + WSASetBlockingHook : LPFN_WSASETBLOCKINGHOOK = nil; + {$EXTERNALSYM WSACancelBlockingCall} + WSACancelBlockingCall : LPFN_WSACANCELBLOCKINGCALL = nil; + {$EXTERNALSYM WSAAsyncGetServByName} + WSAAsyncGetServByName : LPFN_WSAASYNCGETSERVBYNAME = nil; + {$EXTERNALSYM WSAAsyncGetServByPort} + WSAAsyncGetServByPort : LPFN_WSAASYNCGETSERVBYPORT = nil; + {$EXTERNALSYM WSAAsyncGetProtoByName} + WSAAsyncGetProtoByName : LPFN_WSAASYNCGETPROTOBYNAME = nil; + {$EXTERNALSYM WSAAsyncGetProtoByNumber} + WSAAsyncGetProtoByNumber : LPFN_WSAASYNCGETPROTOBYNUMBER = nil; + {$EXTERNALSYM WSAAsyncGetHostByName} + WSAAsyncGetHostByName : LPFN_WSAASYNCGETHOSTBYNAME = nil; + {$EXTERNALSYM WSAAsyncGetHostByAddr} + WSAAsyncGetHostByAddr : LPFN_WSAASYNCGETHOSTBYADDR = nil; + {$EXTERNALSYM WSACancelAsyncRequest} + WSACancelAsyncRequest : LPFN_WSACANCELASYNCREQUEST = nil; + {$EXTERNALSYM WSAAsyncSelect} + WSAAsyncSelect : LPFN_WSAASYNCSELECT = nil; +{$ENDIF} + {$EXTERNALSYM __WSAFDIsSet} + __WSAFDIsSet : LPFN___WSAFDISSET = nil; + {$EXTERNALSYM WSAAccept} + WSAAccept : LPFN_WSAACCEPT = nil; + {$EXTERNALSYM WSAAddressToStringA} + WSAAddressToStringA : LPFN_WSAADDRESSTOSTRINGA = nil; + {$EXTERNALSYM WSAAddressToStringW} + WSAAddressToStringW : LPFN_WSAADDRESSTOSTRINGW = nil; + {$EXTERNALSYM WSAAddressToString} + WSAAddressToString : LPFN_WSAADDRESSTOSTRING = nil; + {$EXTERNALSYM WSACloseEvent} + WSACloseEvent : LPFN_WSACLOSEEVENT = nil; + {$EXTERNALSYM WSAConnect} + WSAConnect : LPFN_WSACONNECT = nil; + {$EXTERNALSYM WSACreateEvent} + WSACreateEvent : LPFN_WSACREATEEVENT = nil; + {$IFNDEF WINCE} + {$EXTERNALSYM WSADuplicateSocketA} + WSADuplicateSocketA : LPFN_WSADUPLICATESOCKETA = nil; + {$EXTERNALSYM WSADuplicateSocketW} + WSADuplicateSocketW : LPFN_WSADUPLICATESOCKETW = nil; + {$EXTERNALSYM WSADuplicateSocket} + WSADuplicateSocket : LPFN_WSADUPLICATESOCKET = nil; + {$ENDIF} + {$EXTERNALSYM WSAEnumNetworkEvents} + WSAEnumNetworkEvents : LPFN_WSAENUMNETWORKEVENTS = nil; + {$EXTERNALSYM WSAEnumProtocolsA} + WSAEnumProtocolsA : LPFN_WSAENUMPROTOCOLSA = nil; + {$EXTERNALSYM WSAEnumProtocolsW} + WSAEnumProtocolsW : LPFN_WSAENUMPROTOCOLSW = nil; + {$EXTERNALSYM WSAEnumProtocols} + WSAEnumProtocols : LPFN_WSAENUMPROTOCOLS = nil; + {$EXTERNALSYM WSAEnumNameSpaceProvidersA} + WSAEnumNameSpaceProvidersA : LPFN_WSAENUMNAMESPACEPROVIDERSA = nil; + {$EXTERNALSYM WSAEnumNameSpaceProvidersW} + WSAEnumNameSpaceProvidersW : LPFN_WSAENUMNAMESPACEPROVIDERSW = nil; + {$EXTERNALSYM WSAEnumNameSpaceProviders} + WSAEnumNameSpaceProviders : LPFN_WSAENUMNAMESPACEPROVIDERS = nil; + {$EXTERNALSYM WSAEventSelect} + WSAEventSelect : LPFN_WSAEVENTSELECT = nil; + {$EXTERNALSYM WSAGetOverlappedResult} + WSAGetOverlappedResult : LPFN_WSAGETOVERLAPPEDRESULT = nil; + + {$EXTERNALSYM WSAGetQosByName} + WSAGetQosByName : LPFN_WSAGETQOSBYNAME = nil; + {$EXTERNALSYM WSAGetServiceClassInfoA} + WSAGetServiceClassInfoA : LPFN_WSAGETSERVICECLASSINFOA = nil; + {$EXTERNALSYM WSAGetServiceClassInfoW} + WSAGetServiceClassInfoW : LPFN_WSAGETSERVICECLASSINFOW = nil; + {$EXTERNALSYM WSAGetServiceClassInfo} + WSAGetServiceClassInfo : LPFN_WSAGETSERVICECLASSINFO = nil; + {$EXTERNALSYM WSAGetServiceClassNameByClassIdA} + WSAGetServiceClassNameByClassIdA : LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDA = nil; + {$EXTERNALSYM WSAGetServiceClassNameByClassIdW} + WSAGetServiceClassNameByClassIdW : LPFN_WSAGETSERVICECLASSNAMEBYCLASSIDW = nil; + {$EXTERNALSYM WSAGetServiceClassNameByClassId} + WSAGetServiceClassNameByClassId : LPFN_WSAGETSERVICECLASSNAMEBYCLASSID = nil; + + {$EXTERNALSYM WSAHtonl} + WSAHtonl : LPFN_WSAHTONL = nil; + {$EXTERNALSYM WSAHtons} + WSAHtons : LPFN_WSAHTONS = nil; + {$EXTERNALSYM WSAIoctl} + WSAIoctl : LPFN_WSAIOCTL = nil; + {$EXTERNALSYM WSAInstallServiceClassA} + WSAInstallServiceClassA : LPFN_WSAINSTALLSERVICECLASSA = nil; + {$EXTERNALSYM WSAInstallServiceClassW} + WSAInstallServiceClassW : LPFN_WSAINSTALLSERVICECLASSW = nil; + {$EXTERNALSYM WSAInstallServiceClass} + WSAInstallServiceClass : LPFN_WSAINSTALLSERVICECLASS = nil; + {$EXTERNALSYM WSAJoinLeaf} + WSAJoinLeaf : LPFN_WSAJOINLEAF = nil; + {$EXTERNALSYM WSALookupServiceBeginA} + WSALookupServiceBeginA : LPFN_WSALOOKUPSERVICEBEGINA = nil; + {$EXTERNALSYM WSALookupServiceBeginW} + WSALookupServiceBeginW : LPFN_WSALOOKUPSERVICEBEGINW = nil; + {$EXTERNALSYM WSALookupServiceBegin} + WSALookupServiceBegin : LPFN_WSALOOKUPSERVICEBEGIN = nil; + {$EXTERNALSYM WSALookupServiceEnd} + WSALookupServiceEnd : LPFN_WSALOOKUPSERVICEEND = nil; + {$EXTERNALSYM WSALookupServiceNextA} + WSALookupServiceNextA : LPFN_WSALOOKUPSERVICENEXTA = nil; + {$EXTERNALSYM WSALookupServiceNextW} + WSALookupServiceNextW : LPFN_WSALOOKUPSERVICENEXTW = nil; + {$EXTERNALSYM WSALookupServiceNext} + WSALookupServiceNext : LPFN_WSALOOKUPSERVICENEXT = nil; + {$EXTERNALSYM WSANtohl} + WSANtohl : LPFN_WSANTOHL = nil; + {$EXTERNALSYM WSANtohs} + WSANtohs : LPFN_WSANTOHS = nil; + {$EXTERNALSYM WSARecv} + WSARecv : LPFN_WSARECV = nil; + {$EXTERNALSYM WSARecvDisconnect} + WSARecvDisconnect : LPFN_WSARECVDISCONNECT = nil; + {$EXTERNALSYM WSARecvFrom} + WSARecvFrom : LPFN_WSARECVFROM = nil; + {$EXTERNALSYM WSARemoveServiceClass} + WSARemoveServiceClass : LPFN_WSAREMOVESERVICECLASS = nil; + {$EXTERNALSYM WSAResetEvent} + WSAResetEvent : LPFN_WSARESETEVENT = nil; + {$EXTERNALSYM WSASend} + WSASend : LPFN_WSASEND = nil; + {$EXTERNALSYM WSASendDisconnect} + WSASendDisconnect : LPFN_WSASENDDISCONNECT = nil; + {$EXTERNALSYM WSASendTo} + WSASendTo : LPFN_WSASENDTO = nil; + {$EXTERNALSYM WSASetEvent} + WSASetEvent : LPFN_WSASETEVENT = nil; + {$EXTERNALSYM WSASetServiceA} + WSASetServiceA : LPFN_WSASETSERVICEA = nil; + {$EXTERNALSYM WSASetServiceW} + WSASetServiceW : LPFN_WSASETSERVICEW = nil; + {$EXTERNALSYM WSASetService} + WSASetService : LPFN_WSASETSERVICE = nil; + {$EXTERNALSYM WSASocketA} + WSASocketA : LPFN_WSASOCKETA = nil; + {$EXTERNALSYM WSASocketW} + WSASocketW : LPFN_WSASOCKETW = nil; + {$EXTERNALSYM WSASocket} + WSASocket : LPFN_WSASOCKET = nil; + {$EXTERNALSYM WSAStringToAddressA} + WSAStringToAddressA : LPFN_WSASTRINGTOADDRESSA = nil; + {$EXTERNALSYM WSAStringToAddressW} + WSAStringToAddressW : LPFN_WSASTRINGTOADDRESSW = nil; + {$EXTERNALSYM WSAStringToAddress} + WSAStringToAddress : LPFN_WSASTRINGTOADDRESS = nil; + + {$EXTERNALSYM WSAWaitForMultipleEvents} + WSAWaitForMultipleEvents : LPFN_WSAWAITFORMULTIPLEEVENTS = nil; + {$EXTERNALSYM WSAProviderConfigChange} + WSAProviderConfigChange : LPFN_WSAPROVIDERCONFIGCHANGE = nil; + {$EXTERNALSYM TransmitFile} + TransmitFile : LPFN_TRANSMITFILE = nil; + {$EXTERNALSYM AcceptEx} + AcceptEx : LPFN_ACCEPTEX = nil; + {$EXTERNALSYM GetAcceptExSockaddrs} + GetAcceptExSockaddrs : LPFN_GETACCEPTEXSOCKADDRS = nil; + {$IFNDEF WINCE} + //This is defined in the Winapi.Windows Mobile 6 Standard SDK Refresh + //but I'm not sure what .DLL the function is in. I also couldn't find a WSAID + //constant for it. + {$EXTERNALSYM WSARecvEx} + WSARecvEx : LPFN_WSARECVEX = nil; + {$ENDIF} + {$EXTERNALSYM ConnectEx} + ConnectEx : LPFN_CONNECTEX = nil; + {$EXTERNALSYM DisconnectEx} + DisconnectEx : LPFN_DISCONNECTEX = nil; + {$EXTERNALSYM WSARecvMsg} + WSARecvMsg : LPFN_WSARECVMSG = nil; + {$EXTERNALSYM TransmitPackets} + TransmitPackets : LPFN_TRANSMITPACKETS = nil; + {$IFNDEF WINCE} + //Winapi.Windows Vista, Winapi.Windows Server 2008 + {$EXTERNALSYM WSASendMsg} + WSASendMsg: LPFN_WSASENDMSG = nil; + {$EXTERNALSYM WSAPoll} + WSAPoll: LPFN_WSAPOLL = nil; + {$ENDIF} + //WSANSPIoctl is not supported in WinCE 4.20 but is supported in later versions. + {$EXTERNALSYM WSANSPIoctl} + WSANSPIoctl : LPFN_WSANSPIOCTL = nil; +{$ENDIF} // $IFDEF WS2_DLL_FUNC_VARS + + { Macros } + {$EXTERNALSYM WSAMakeSyncReply} + function WSAMakeSyncReply(Buflen, AError: Word): Longint; + {$EXTERNALSYM WSAMakeSelectReply} + function WSAMakeSelectReply(Event, AError: Word): Longint; + {$EXTERNALSYM WSAGetAsyncBuflen} + function WSAGetAsyncBuflen(Param: Longint): Word; + {$EXTERNALSYM WSAGetAsyncError} + function WSAGetAsyncError(Param: Longint): Word; + {$EXTERNALSYM WSAGetSelectEvent} + function WSAGetSelectEvent(Param: Longint): Word; + {$EXTERNALSYM WSAGetSelectError} + function WSAGetSelectError(Param: Longint): Word; + + {$EXTERNALSYM FD_CLR} + procedure FD_CLR(ASocket: TSocket; var FDSet: TFDSet); + {$EXTERNALSYM FD_ISSET} + function FD_ISSET(ASocket: TSocket; var FDSet: TFDSet): Boolean; + {$EXTERNALSYM FD_SET} + procedure FD_SET(ASocket: TSocket; var FDSet: TFDSet); + {$EXTERNALSYM FD_ZERO} + procedure FD_ZERO(var FDSet: TFDSet); + + {$IFNDEF WINCE} +//Posix aliases for helper macros +// #define CMSGHDR_ALIGN WSA_CMSGHDR_ALIGN + {$EXTERNALSYM CMSGHDR_ALIGN} + function CMSGHDR_ALIGN(const Alength: SIZE_T): SIZE_T; +// #define CMSGDATA_ALIGN WSA_CMSGDATA_ALIGN + {$EXTERNALSYM CMSGDATA_ALIGN} + function CMSGDATA_ALIGN(const Alength: UINT_PTR): UINT_PTR; +//#define CMSG_FIRSTHDR WSA_CMSG_FIRSTHDR + {$EXTERNALSYM CMSG_FIRSTHDR} + function CMSG_FIRSTHDR(const msg: LPWSAMSG): LPWSACMSGHDR; +// #define CMSG_NXTHDR WSA_CMSG_NXTHDR + {$EXTERNALSYM CMSG_NXTHDR} + function CMSG_NXTHDR(const msg: LPWSAMSG; const cmsg: LPWSACMSGHDR): LPWSACMSGHDR; +// #define CMSG_SPACE WSA_CMSG_SPACE + {$EXTERNALSYM CMSG_SPACE} + function CMSG_SPACE(const Alength: UINT_PTR): UINT_PTR; +// #define CMSG_LEN WSA_CMSG_LEN + {$EXTERNALSYM CMSG_LEN} + function CMSG_LEN(const Alength: SIZE_T): SIZE_T; +// + {$EXTERNALSYM WSA_CMSGHDR_ALIGN} + function WSA_CMSGHDR_ALIGN(const Alength: UINT_PTR): UINT_PTR; + {$EXTERNALSYM WSA_CMSGDATA_ALIGN} + function WSA_CMSGDATA_ALIGN(const Alength: UINT_PTR): UINT_PTR; + {$EXTERNALSYM WSA_CMSG_FIRSTHDR} + function WSA_CMSG_FIRSTHDR(const msg: LPWSAMSG): LPWSACMSGHDR; +// #define CMSG_FIRSTHDR WSA_CMSG_FIRSTHDR + {$EXTERNALSYM WSA_CMSG_NXTHDR} + function WSA_CMSG_NXTHDR(const msg: LPWSAMSG; const cmsg: LPWSACMSGHDR): LPWSACMSGHDR; + + {$EXTERNALSYM WSA_CMSG_DATA} + function WSA_CMSG_DATA(const cmsg: LPWSACMSGHDR): PByte; + {$EXTERNALSYM WSA_CMSG_SPACE} + function WSA_CMSG_SPACE(const Alength: SIZE_T): SIZE_T; + {$EXTERNALSYM WSA_CMSG_LEN} + function WSA_CMSG_LEN(const Alength: SIZE_T): SIZE_T; + {$ENDIF} + +//============================================================= + +{ + WS2TCPIP.H - WinSock2 Extension for TCP/IP protocols + + This file contains TCP/IP specific information for use + by WinSock2 compatible applications. + + Copyright (c) 1995-1999 Microsoft Corporation + + To provide the backward compatibility, all the TCP/IP + specific definitions that were included in the WINSOCK.H + file are now included in WINSOCK2.H file. WS2TCPIP.H + file includes only the definitions introduced in the + "WinSock 2 Protocol-Specific Annex" document. + + Rev 0.3 Nov 13, 1995 + Rev 0.4 Dec 15, 1996 +} + +type +// Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP + {$EXTERNALSYM ip_mreq} + ip_mreq = record + imr_multiaddr : TInAddr; // IP multicast address of group + imr_interface : TInAddr; // local IP address of interface + end; + +// Argument structure for IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP, +// IP_BLOCK_SOURCE, and IP_UNBLOCK_SOURCE + {$EXTERNALSYM ip_mreq_source} + ip_mreq_source = record + imr_multiaddr: TInAddr; // IP multicast address of group + imr_sourceaddr: TInAddr; // IP address of source + imr_interface: TInAddr; // local IP address of interface + end; + +// Argument structure for SIO_{GET,SET}_MULTICAST_FILTER + {$EXTERNALSYM ip_msfilter} + ip_msfilter = record + imsf_multiaddr: TInAddr; // IP multicast address of group + imsf_interface: TInAddr; // local IP address of interface + imsf_fmode: u_long; // filter mode - INCLUDE or EXCLUDE + imsf_numsrc: u_long; // number of sources in src_list + imsf_slist: Array[0..0] of TInAddr; + end; + + {$EXTERNALSYM IP_MSFILTER_SIZE} + function IP_MSFILTER_SIZE(const numsrc: DWORD): UINT_PTR; + +// TCP/IP specific Ioctl codes +const + {$EXTERNALSYM SIO_GET_INTERFACE_LIST} + SIO_GET_INTERFACE_LIST = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or 127; {Do not Localize} +// New IOCTL with address size independent address array + {$EXTERNALSYM SIO_GET_INTERFACE_LIST_EX} + SIO_GET_INTERFACE_LIST_EX = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or 126; {Do not Localize} + {$EXTERNALSYM SIO_SET_MULTICAST_FILTER} + SIO_SET_MULTICAST_FILTER = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or 125; {Do not Localize} + {$EXTERNALSYM SIO_GET_MULTICAST_FILTER} + SIO_GET_MULTICAST_FILTER = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or (124 or IOC_IN); {Do not Localize} + {$EXTERNALSYM SIOCSIPMSFILTER} + SIOCSIPMSFILTER = SIO_SET_MULTICAST_FILTER; + {$EXTERNALSYM SIOCGIPMSFILTER} + SIOCGIPMSFILTER = SIO_GET_MULTICAST_FILTER; +// +// Protocol independent ioctls for setting and retrieving multicast filters. +// + {$EXTERNALSYM SIOCSMSFILTER} + SIOCSMSFILTER = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or 126; {Do not Localize} + {$EXTERNALSYM SIOCGMSFILTER} + SIOCGMSFILTER = IOC_IN or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or (127 or IOC_IN); {Do not Localize} + + {$IFNDEF WINCE} + //Winapi.Windows 2008 and Winapi.Windows Vista SP1 additions + {$EXTERNALSYM SIO_IDEAL_SEND_BACKLOG_QUERY} + SIO_IDEAL_SEND_BACKLOG_QUERY = IOC_OUT or ((SizeOf(u_long) and IOCPARM_MASK) shl 16) or (Ord('t') shl 8) or 123; + {$EXTERNALSYM SIO_IDEAL_SEND_BACKLOG_CHANGE} + SIO_IDEAL_SEND_BACKLOG_CHANGE = IOC_VOID or (Ord('t') shl 8) or 122; + {$ENDIF} + + {$IFNDEF WINCE} +// Options for use with [gs]etsockopt at the IP level. + {$EXTERNALSYM IP_OPTIONS} + IP_OPTIONS = 1; // set/get IP options + {$EXTERNALSYM IP_HDRINCL} + IP_HDRINCL = 2; // header is included with data + {$EXTERNALSYM IP_TOS} + IP_TOS = 3; // IP type of service and preced + {$EXTERNALSYM IP_TTL} + IP_TTL = 4; // IP time to live + {$EXTERNALSYM IP_MULTICAST_IF} + IP_MULTICAST_IF = 9; // set/get IP multicast i/f + {$EXTERNALSYM IP_MULTICAST_TTL} + IP_MULTICAST_TTL = 10; // set/get IP multicast ttl + {$EXTERNALSYM IP_MULTICAST_LOOP} + IP_MULTICAST_LOOP = 11; // set/get IP multicast loopback + {$EXTERNALSYM IP_ADD_MEMBERSHIP} + IP_ADD_MEMBERSHIP = 12; // add an IP group membership + {$EXTERNALSYM IP_DROP_MEMBERSHIP} + IP_DROP_MEMBERSHIP = 13; // drop an IP group membership + {$ELSE} + {$EXTERNALSYM IP_TOS} + IP_TOS = 8; //* IP type of service and preced*/ + {$EXTERNALSYM IP_TTL} + IP_TTL = 7; //* IP time to live */ + {$EXTERNALSYM IP_MULTICAST_IF} + IP_MULTICAST_IF = 2; //* set/get IP multicast i/f */ + {$EXTERNALSYM IP_MULTICAST_TTL} + IP_MULTICAST_TTL = 3; //* set/get IP multicast ttl */ + {$EXTERNALSYM IP_MULTICAST_LOOP} + IP_MULTICAST_LOOP = 4; //*set/get IP multicast loopback */ + {$EXTERNALSYM IP_ADD_MEMBERSHIP} + IP_ADD_MEMBERSHIP = 5; //* add an IP group membership */ + {$EXTERNALSYM IP_DROP_MEMBERSHIP} + IP_DROP_MEMBERSHIP = 6; //* drop an IP group membership */ + //JPM Notes. IP_HDRINCL is not supported in WinCE 4.0. + {$EXTERNALSYM IP_HDRINCL} + IP_HDRINCL = 9; //* header is included with data */ + {$ENDIF} + + {$EXTERNALSYM IP_DONTFRAGMENT} + IP_DONTFRAGMENT = 14; // don't fragment IP datagrams {Do not Localize} + {$EXTERNALSYM IP_ADD_SOURCE_MEMBERSHIP} + IP_ADD_SOURCE_MEMBERSHIP = 15; // join IP group/source + {$EXTERNALSYM IP_DROP_SOURCE_MEMBERSHIP} + IP_DROP_SOURCE_MEMBERSHIP = 16; // leave IP group/source + {$EXTERNALSYM IP_BLOCK_SOURCE} + IP_BLOCK_SOURCE = 17; // block IP group/source + {$EXTERNALSYM IP_UNBLOCK_SOURCE} + IP_UNBLOCK_SOURCE = 18; // unblock IP group/source + {$EXTERNALSYM IP_PKTINFO} + IP_PKTINFO = 19; // receive packet information for ipv4 + {$EXTERNALSYM IP_RECEIVE_BROADCAST} + IP_RECEIVE_BROADCAST = 22; // Allow/block broadcast reception. + {$EXTERNALSYM IP_RECVIF} + IP_RECVIF = 24; // Receive arrival interface. + {$EXTERNALSYM IP_RECVDSTADDR} + IP_RECVDSTADDR = 25; // Receive destination address. + {$EXTERNALSYM IP_IFLIST} + IP_IFLIST = 28; // Enable/Disable an interface list. + {$EXTERNALSYM IP_ADD_IFLIST} + IP_ADD_IFLIST = 29; // Add an interface list entry. + {$EXTERNALSYM IP_DEL_IFLIST} + IP_DEL_IFLIST = 30; // Delete an interface list entry. + {$EXTERNALSYM IP_UNICAST_IF} + IP_UNICAST_IF = 31; // IP unicast interface. + {$EXTERNALSYM IP_RTHDR} + IP_RTHDR = 32; // Set/get IPv6 routing header. + {$EXTERNALSYM IP_RECVRTHDR} + IP_RECVRTHDR = 38; // Receive the routing header. + {$EXTERNALSYM IP_TCLASS} + IP_TCLASS = 39; // Packet traffic class. + {$EXTERNALSYM IP_RECVTCLASS} + IP_RECVTCLASS = 40; // Receive packet traffic class. + {$EXTERNALSYM IP_ORIGINAL_ARRIVAL_IF} + IP_ORIGINAL_ARRIVAL_IF = 47; // Original Arrival Interface Index. (Winapi.Windows 7) + + + {$IFDEF WINCE} + {$EXTERNALSYM IP_DSCP_TRAFFIC_TYPE} + IP_DSCP_TRAFFIC_TYPE = 100; //* differential services */ + {$EXTERNALSYM IP_RELOAD_DSCP_MAPPINGS} + IP_RELOAD_DSCP_MAPPINGS = 101; //* reload DSCP registry mappings */ + {$ENDIF} + + {$EXTERNALSYM IP_DEFAULT_MULTICAST_TTL} + IP_DEFAULT_MULTICAST_TTL = 1; // normally limit m'casts to 1 hop {Do not Localize} + {$EXTERNALSYM IP_DEFAULT_MULTICAST_LOOP} + IP_DEFAULT_MULTICAST_LOOP = 1; // normally hear sends if a member + {$EXTERNALSYM IP_MAX_MEMBERSHIPS} + IP_MAX_MEMBERSHIPS = 20; // per socket; must fit in one mbuf + + + // Option to use with [gs]etsockopt at the IPPROTO_IPV6 level + {$EXTERNALSYM IPV6_HDRINCL} + IPV6_HDRINCL = 2; // Header is included with data + {$EXTERNALSYM IPV6_UNICAST_HOPS} + IPV6_UNICAST_HOPS = 4; // Set/get IP unicast hop limit + {$EXTERNALSYM IPV6_MULTICAST_IF} + IPV6_MULTICAST_IF = 9; // Set/get IP multicast interface + {$EXTERNALSYM IPV6_MULTICAST_HOPS} + IPV6_MULTICAST_HOPS = 10; // Set/get IP multicast ttl + {$EXTERNALSYM IPV6_MULTICAST_LOOP} + IPV6_MULTICAST_LOOP = 11; // Set/get IP multicast loopback + {$EXTERNALSYM IPV6_ADD_MEMBERSHIP} + IPV6_ADD_MEMBERSHIP = 12; // Add an IP group membership + {$EXTERNALSYM IPV6_DROP_MEMBERSHIP} + IPV6_DROP_MEMBERSHIP = 13; // Drop an IP group membership + {$EXTERNALSYM IPV6_JOIN_GROUP} + IPV6_JOIN_GROUP = IPV6_ADD_MEMBERSHIP; + {$EXTERNALSYM IPV6_LEAVE_GROUP} + IPV6_LEAVE_GROUP = IPV6_DROP_MEMBERSHIP; + {$EXTERNALSYM IPV6_PKTINFO} + IPV6_PKTINFO = 19; // Receive packet information for ipv6 + {$EXTERNALSYM IPV6_HOPLIMIT} + IPV6_HOPLIMIT = 21; // Receive packet hop limit + //Note that IPV6_PROTECTION_LEVEL is not supported for WinCE 4.2 + {$EXTERNALSYM IPV6_PROTECTION_LEVEL} + IPV6_PROTECTION_LEVEL = 23; // Set/get IPv6 protection level + + // Option to use with [gs]etsockopt at the IPPROTO_UDP level + {$EXTERNALSYM UDP_NOCHECKSUM} + UDP_NOCHECKSUM = 1; + {$EXTERNALSYM UDP_CHECKSUM_COVERAGE} + UDP_CHECKSUM_COVERAGE = 20; // Set/get UDP-Lite checksum coverage + +// Option to use with [gs]etsockopt at the IPPROTO_TCP level + {$EXTERNALSYM TCP_EXPEDITED_1122} + TCP_EXPEDITED_1122 = $0002; + +// IPv6 definitions +type + {$EXTERNALSYM IN6_ADDR} + IN6_ADDR = record + case Integer of + 0: (s6_addr: array[0..15] of u_char); + 1: (word: array[0..7] of u_short); + end; + {$NODEFINE TIn6Addr} + TIn6Addr = IN6_ADDR; + {$NODEFINE PIn6Addr} + PIn6Addr = ^TIn6Addr; + {$EXTERNALSYM PIN6_ADDR} + PIN6_ADDR = ^PIn6Addr; + {$EXTERNALSYM LPIN6_ADDR} + LPIN6_ADDR = PIN6_ADDR; + +{$IFNDEF WINCE} + {$IFNDEF NO_REDECLARE} + // Argument structure for IPV6_JOIN_GROUP and IPV6_LEAVE_GROUP + {$EXTERNALSYM ipv6_mreq} + ipv6_mreq = record + ipv6mr_multiaddr: TIn6Addr; // IPv6 multicast address + ipv6mr_interface: u_int; // Interface index + end; + {$NODEFINE TIPv6_MReq} + TIPv6_MReq = IPV6_MREQ; + {$NODEFINE PIPv6_MReq} + PIPv6_MReq = ^TIPv6_MReq; + {$ENDIF} +{$ENDIF} + + // Old IPv6 socket address structure (retained for sockaddr_gen definition below) + {$EXTERNALSYM sockaddr_in6_old} + sockaddr_in6_old = record + sin6_family : Smallint; // AF_INET6 + sin6_port : u_short; // Transport level port number + sin6_flowinfo : u_long; // IPv6 flow information + sin6_addr : TIn6Addr; // IPv6 address + end; + +// IPv6 socket address structure, RFC 2553 + {$EXTERNALSYM SOCKADDR_IN6} + SOCKADDR_IN6 = record + sin6_family : Smallint; // AF_INET6 + sin6_port : u_short; // Transport level port number + sin6_flowinfo : u_long; // IPv6 flow information + sin6_addr : TIn6Addr; // IPv6 address + sin6_scope_id : u_long; // set of interfaces for a scope + end; + {$NODEFINE TSockAddrIn6} + TSockAddrIn6 = SOCKADDR_IN6; + {$NODEFINE PSockAddrIn6} + PSockAddrIn6 = ^TSockAddrIn6; + {$EXTERNALSYM PSOCKADDR_IN6} + PSOCKADDR_IN6 = PSockAddrIn6; + {$EXTERNALSYM LPSOCKADDR_IN6} + LPSOCKADDR_IN6 = PSOCKADDR_IN6; + + {$EXTERNALSYM sockaddr_gen} + sockaddr_gen = record + case Integer of + 1 : ( Address : TSockAddr; ); + 2 : ( AddressIn : TSockAddrIn; ); + 3 : ( AddressIn6 : sockaddr_in6_old; ); + end; + {$NODEFINE TSockAddrGen} + TSockAddrGen = sockaddr_gen; + +// Structure to keep interface specific information + {$EXTERNALSYM INTERFACE_INFO} + INTERFACE_INFO = record + iiFlags : u_long; // Interface flags + iiAddress : TSockAddrGen; // Interface address + iiBroadcastAddress : TSockAddrGen; // Broadcast address + iiNetmask : TSockAddrGen; // Network mask + end; + {$NODEFINE TInterface_Info} + TInterface_Info = INTERFACE_INFO; + {$EXTERNALSYM PINTERFACE_INFO} + PINTERFACE_INFO = ^TInterface_Info; + {$EXTERNALSYM LPINTERFACE_INFO} + LPINTERFACE_INFO = PINTERFACE_INFO; + +// New structure that does not have dependency on the address size + {$EXTERNALSYM INTERFACE_INFO_EX} + INTERFACE_INFO_EX = record + iiFlags : u_long; // Interface flags + iiAddress : TSocket_Address; // Interface address + iiBroadcastAddress : TSocket_Address; // Broadcast address + iiNetmask : TSocket_Address; // Network mask + end; + {$NODEFINE TInterface_Info_Ex} + TInterface_Info_Ex = INTERFACE_INFO_EX; + {$EXTERNALSYM PINTERFACE_INFO_EX} + PINTERFACE_INFO_EX = ^TInterface_Info_Ex; + {$EXTERNALSYM LPINTERFACE_INFO_EX} + LPINTERFACE_INFO_EX = PINTERFACE_INFO_EX; + +// Macro that works for both IPv4 and IPv6 + {$EXTERNALSYM SS_PORT} + function SS_PORT(ssp: PSockAddrIn): u_short; + + {$EXTERNALSYM IN6ADDR_ANY_INIT} + function IN6ADDR_ANY_INIT: TIn6Addr; + {$EXTERNALSYM IN6ADDR_LOOPBACK_INIT} + function IN6ADDR_LOOPBACK_INIT: TIn6Addr; + + {$EXTERNALSYM IN6ADDR_SETANY} + procedure IN6ADDR_SETANY(sa: PSockAddrIn6); + {$EXTERNALSYM IN6ADDR_SETLOOPBACK} + procedure IN6ADDR_SETLOOPBACK(sa: PSockAddrIn6); + {$EXTERNALSYM IN6ADDR_ISANY} + function IN6ADDR_ISANY(sa: PSockAddrIn6): Boolean; + {$EXTERNALSYM IN6ADDR_ISLOOPBACK} + function IN6ADDR_ISLOOPBACK(sa: PSockAddrIn6): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_SUBNET_ROUTER_ANYCAST} + function IN6_IS_ADDR_SUBNET_ROUTER_ANYCAST(const a : PIn6Addr) : Boolean; + {$EXTERNALSYM IN6_IS_ADDR_SUBNET_RESERVED_ANYCAST} + function IN6_IS_ADDR_SUBNET_RESERVED_ANYCAST(const a: PIn6Addr) : Boolean; + {$EXTERNALSYM IN6_IS_ADDR_ANYCAST} + function IN6_IS_ADDR_ANYCAST(const a: PIn6Addr) : Boolean; + {$EXTERNALSYM IN6_ADDR_EQUAL} + function IN6_ADDR_EQUAL(const a: PIn6Addr; const b: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_UNSPECIFIED} + function IN6_IS_ADDR_UNSPECIFIED(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_LOOPBACK} + function IN6_IS_ADDR_LOOPBACK(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_MULTICAST} + function IN6_IS_ADDR_MULTICAST(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_EUI64} + function IN6_IS_ADDR_EUI64(const a : PIn6Addr) : Boolean; + + {$EXTERNALSYM IN6_IS_ADDR_LINKLOCAL} + function IN6_IS_ADDR_LINKLOCAL(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_SITELOCAL} + function IN6_IS_ADDR_SITELOCAL(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_V4MAPPED} + function IN6_IS_ADDR_V4MAPPED(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_V4COMPAT} + function IN6_IS_ADDR_V4COMPAT(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_MC_NODELOCAL} + function IN6_IS_ADDR_MC_NODELOCAL(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_MC_LINKLOCAL} + function IN6_IS_ADDR_MC_LINKLOCAL(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_MC_SITELOCAL} + function IN6_IS_ADDR_MC_SITELOCAL(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_MC_ORGLOCAL} + function IN6_IS_ADDR_MC_ORGLOCAL(const a: PIn6Addr): Boolean; + {$EXTERNALSYM IN6_IS_ADDR_MC_GLOBAL} + function IN6_IS_ADDR_MC_GLOBAL(const a: PIn6Addr): Boolean; + + {$EXTERNALSYM IN6_SET_ADDR_UNSPECIFIED} + procedure IN6_SET_ADDR_UNSPECIFIED(a : PIN6_ADDR); + +// Possible flags for the iiFlags - bitmask +const + {$EXTERNALSYM IFF_UP} + IFF_UP = $00000001; // Interface is up + {$EXTERNALSYM IFF_BROADCAST} + IFF_BROADCAST = $00000002; // Broadcast is supported + {$EXTERNALSYM IFF_LOOPBACK} + IFF_LOOPBACK = $00000004; // this is loopback interface + {$EXTERNALSYM IFF_POINTTOPOINT} + IFF_POINTTOPOINT = $00000008; // this is point-to-point interface + {$EXTERNALSYM IFF_MULTICAST} + IFF_MULTICAST = $00000010; // multicast is supported + +type + {$EXTERNALSYM MULTICAST_MODE_TYPE} + {$EXTERNALSYM MCAST_INCLUDE} + {$EXTERNALSYM MCAST_EXCLUDE} + MULTICAST_MODE_TYPE = (MCAST_INCLUDE, MCAST_EXCLUDE); + {$EXTERNALSYM GROUP_FILTER} + GROUP_FILTER = record + gf_interface : PULONG; // Interface index. + gf_group : SOCKADDR_STORAGE; // Multicast address. + gf_fmode : MULTICAST_MODE_TYPE; // Filter mode. + gf_numsrc : ULONG; // Number of sources. + gf_slist : SOCKADDR_STORAGE; //gf_slist[1] : SOCKADDR_STORAGE; // Source address. + end; + {$EXTERNALSYM PGROUP_FILTER} + PGROUP_FILTER = ^GROUP_FILTER; + + {$EXTERNALSYM GROUP_REQ} + GROUP_REQ = record + gr_interface : ULONG; // Interface index. + gr_group : SOCKADDR_STORAGE; // Multicast address. + end; + {$EXTERNALSYM PGROUP_REQ} + PGROUP_REQ = ^GROUP_REQ; + + {$EXTERNALSYM GROUP_SOURCE_REQ} + GROUP_SOURCE_REQ = record + gsr_interface : ULONG; // Interface index. + gsr_group : SOCKADDR_STORAGE; // Group address. + gsr_source : SOCKADDR_STORAGE; // Source address. + end; + {$EXTERNALSYM PGROUP_SOURCE_REQ} + PGROUP_SOURCE_REQ = ^GROUP_SOURCE_REQ; + +{$EXTERNALSYM GROUP_FILTER_SIZE} +function GROUP_FILTER_SIZE(const numsrc : DWord) : UINT_PTR; + +type + {$EXTERNALSYM WSAQUERYSET2} + WSAQUERYSET2 = record + dwSize : DWORD; + lpszServiceInstanceName : LPTSTR; + lpVersion : LPWSAVERSION; + lpszComment : LPTSTR; + dwNameSpace : DWORD; + lpNSProviderId : LPGUID; + lpszContext : LPTSTR; + dwNumberOfProtocols : DWORD; + lpafpProtocols : LPAFPROTOCOLS; + lpszQueryString : LPTSTR; + dwNumberOfCsAddrs : DWORD; + lpcsaBuffer : LPCSADDR_INFO; + dwOutputFlags : DWORD; + lpBlob : LPBLOB; + end; + {$EXTERNALSYM PWSAQUERYSET2} + PWSAQUERYSET2 = ^WSAQUERYSET2; + {$EXTERNALSYM LPWSAQUERYSET2} + LPWSAQUERYSET2 = PWSAQUERYSET2; + + {$EXTERNALSYM NAPI_PROVIDER_TYPE} + {$EXTERNALSYM ProviderType_Application} + {$EXTERNALSYM ProviderType_Service} + // The Pascal compiler in Delphi/BCB prior to v6 does not + // support specifying values for individual enum items + {$IFDEF HAS_ENUM_ELEMENT_VALUES} + NAPI_PROVIDER_TYPE = (ProviderType_Application = 1, ProviderType_Service); + {$ELSE} + NAPI_PROVIDER_TYPE = (nptUnused, ProviderType_Application, ProviderType_Service); + {$ENDIF} + {$EXTERNALSYM NAPI_DOMAIN_DESCRIPTION_BLOB} + NAPI_DOMAIN_DESCRIPTION_BLOB = record + AuthLevel : DWORD; + cchDomainName : DWORD; + OffsetNextDomainDescription : DWORD; + OffsetThisDomainName : DWORD; + end; + {$EXTERNALSYM PNAPI_DOMAIN_DESCRIPTION_BLOB} + PNAPI_DOMAIN_DESCRIPTION_BLOB = ^NAPI_DOMAIN_DESCRIPTION_BLOB; + + {$EXTERNALSYM NAPI_PROVIDER_LEVEL} + {$EXTERNALSYM PROVIDERLEVEL_NONE} + {$EXTERNALSYM PROVIDERLEVEL_SECONDARY} + {$EXTERNALSYM PROVIDERLEVEL_PRIMARY} + NAPI_PROVIDER_LEVEL = (PROVIDERLEVEL_NONE, PROVIDERLEVEL_SECONDARY,PROVIDERLEVEL_PRIMARY); + + {$EXTERNALSYM NAPI_PROVIDER_INSTALLATION_BLOB} + NAPI_PROVIDER_INSTALLATION_BLOB = record + dwVersion : DWORD; + dwProviderType : DWORD; + fSupportsWildCard : DWORD; + cDomains : DWORD; + OffsetFirstDomain : DWORD; + end; + {$EXTERNALSYM PNAPI_PROVIDER_INSTALLATION_BLOB} + PNAPI_PROVIDER_INSTALLATION_BLOB = ^NAPI_PROVIDER_INSTALLATION_BLOB; + + {$IFNDEF NOREDECLARE} + {$EXTERNALSYM SERVICE_ADDRESS} + SERVICE_ADDRESS = record + dwAddressType : DWORD; + dwAddressFlags : DWORD; + dwAddressLength : DWORD; + dwPrincipalLength : DWORD; + lpAddress : PByte; + lpPrincipal : PByte; + end; + {$ENDIF} + {$EXTERNALSYM PSERVICE_ADDRESS} + PSERVICE_ADDRESS = ^SERVICE_ADDRESS; + {$EXTERNALSYM LPSERVICE_ADDRESS} + LPSERVICE_ADDRESS = PSERVICE_ADDRESS; + + {$IFNDEF NOREDECLARE} + {$EXTERNALSYM SERVICE_ADDRESSES} + SERVICE_ADDRESSES = record + dwAddressCount : DWORD; +//#ifdef MIDL_PASS +// [size_is(dwAddressCount)] SERVICE_ADDRESS Addressses[*]; +//#else // MIDL_PASS +// SERVICE_ADDRESS Addresses[1] ; +//#endif // MIDL_PASS + Addresses : SERVICE_ADDRESS; + end; + {$EXTERNALSYM PSERVICE_ADDRESSES} + PSERVICE_ADDRESSES = ^SERVICE_ADDRESSES; + {$EXTERNALSYM LPSERVICE_ADDRESSES} + LPSERVICE_ADDRESSES = PSERVICE_ADDRESSES; + {$ENDIF} + +{$IFNDEF VCL_2007_OR_ABOVE} +const + {$EXTERNALSYM RESOURCEDISPLAYTYPE_GENERIC} + RESOURCEDISPLAYTYPE_GENERIC = $00000000; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_DOMAIN} + RESOURCEDISPLAYTYPE_DOMAIN = $00000001; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_SERVER} + RESOURCEDISPLAYTYPE_SERVER = $00000002; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_SHARE} + RESOURCEDISPLAYTYPE_SHARE = $00000003; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_FILE} + RESOURCEDISPLAYTYPE_FILE = $00000004; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_GROUP} + RESOURCEDISPLAYTYPE_GROUP = $00000005; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_NETWORK} + RESOURCEDISPLAYTYPE_NETWORK = $00000006; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_ROOT} + RESOURCEDISPLAYTYPE_ROOT = $00000007; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_SHAREADMIN} + RESOURCEDISPLAYTYPE_SHAREADMIN = $00000008; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_DIRECTORY} + RESOURCEDISPLAYTYPE_DIRECTORY = $00000009; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_TREE} + RESOURCEDISPLAYTYPE_TREE = $0000000A; + {$EXTERNALSYM RESOURCEDISPLAYTYPE_NDSCONTAINER} + RESOURCEDISPLAYTYPE_NDSCONTAINER = $0000000B; +{$ENDIF} + +type + {$EXTERNALSYM SERVICE_TYPE_VALUE_ABSA} + SERVICE_TYPE_VALUE_ABSA = record + dwNameSpace : DWORD; + dwValueType : DWORD; + dwValueSize : DWORD; + lpValueName : LPSTR; + lpValue : Pointer; + end; + {$EXTERNALSYM PSERVICE_TYPE_VALUE_ABSA} + PSERVICE_TYPE_VALUE_ABSA = ^SERVICE_TYPE_VALUE_ABSA; + {$EXTERNALSYM LPSERVICE_TYPE_VALUE_ABSA} + LPSERVICE_TYPE_VALUE_ABSA = PSERVICE_TYPE_VALUE_ABSA; + + {$EXTERNALSYM SERVICE_INFOA} + SERVICE_INFOA = record + lpServiceType : LPGUID; + lpServiceName : PAnsiChar; + lpComment : PAnsiChar; + lpLocale : PAnsiChar; + dwDisplayHint : DWORD; + dwVersion : DWORD; + dwTime : DWORD; + lpMachineName : PAnsiChar; + lpServiceAddress : LPSERVICE_ADDRESSES; + ServiceSpecificInfo : BLOB; + end; + {$EXTERNALSYM SERVICE_INFOW} + SERVICE_INFOW = record + lpServiceType : LPGUID; + lpServiceName : PWideChar; + lpComment : PWideChar; + lpLocale : PWideChar; + dwDisplayHint : DWORD; + dwVersion : DWORD; + dwTime : DWORD; + lpMachineName : PWideChar; + lpServiceAddress : LPSERVICE_ADDRESSES; + ServiceSpecificInfo : BLOB; + end; + + {$EXTERNALSYM SOCKET_USAGE_TYPE} + {$EXTERNALSYM SYSTEM_CRITICAL_SOCKET} + // The Pascal compiler in Delphi/BCB prior to v6 does not + // support specifying values for individual enum items + {$IFDEF HAS_ENUM_ELEMENT_VALUES} + SOCKET_USAGE_TYPE = (SYSTEM_CRITICAL_SOCKET = 1); + {$ELSE} + SOCKET_USAGE_TYPE = (sutUnused, SYSTEM_CRITICAL_SOCKET); + {$ENDIF} + + {$IFNDEF NO_REDECLARE} + {$EXTERNALSYM SERVICE_INFO} + {$IFDEF UNICODE} + SERVICE_INFO = SERVICE_INFOW; + {$ELSE} + SERVICE_INFO = SERVICE_INFOA; + {$ENDIF} + {$ENDIF} + {$EXTERNALSYM NS_SERVICE_INFOA} + NS_SERVICE_INFOA = record + dwNameSpace : DWORD; + ServiceInfo : SERVICE_INFOA; + end; + {$EXTERNALSYM PNS_SERVICE_INFOA} + PNS_SERVICE_INFOA = ^NS_SERVICE_INFOA; + {$EXTERNALSYM LPNS_SERVICE_INFOA} + LPNS_SERVICE_INFOA = NS_SERVICE_INFOA; + {$EXTERNALSYM NS_SERVICE_INFOW} + NS_SERVICE_INFOW = record + dwNameSpace : DWORD; + ServiceInfo : SERVICE_INFOW; + end; + {$EXTERNALSYM PNS_SERVICE_INFOW} + PNS_SERVICE_INFOW = ^NS_SERVICE_INFOW; + {$EXTERNALSYM LPNS_SERVICE_INFOW} + LPNS_SERVICE_INFOW = NS_SERVICE_INFOW; + {$IFNDEF NO_REDECLARE} + {$EXTERNALSYM NS_SERVICE_INFO} + {$EXTERNALSYM PNS_SERVICE_INFO} + {$EXTERNALSYM LPNS_SERVICE_INFO} + {$IFDEF UNICODE} + NS_SERVICE_INFO = NS_SERVICE_INFOW; + PNS_SERVICE_INFO = PNS_SERVICE_INFOW; + LPNS_SERVICE_INFO = LPNS_SERVICE_INFOW; + {$ELSE} + NS_SERVICE_INFO = NS_SERVICE_INFOA; + PNS_SERVICE_INFO = PNS_SERVICE_INFOA; + LPNS_SERVICE_INFO = LPNS_SERVICE_INFOA; + {$ENDIF} + {$ENDIF} + +{$IFNDEF WINCE} +type +// structure for IP_PKTINFO option + {$EXTERNALSYM IN_PKTINFO} + IN_PKTINFO = record + ipi_addr : TInAddr; // destination IPv4 address + ipi_ifindex : UINT; // received interface index + end; + {$NODEFINE TInPktInfo} + TInPktInfo = IN_PKTINFO; + {$NODEFINE PInPktInfo} + PInPktInfo = ^IN_PKTINFO; + +// structure for IPV6_PKTINFO option + {$EXTERNALSYM IN6_PKTINFO} + IN6_PKTINFO = record + ipi6_addr : TIn6Addr; // destination IPv6 address + ipi6_ifindex : UINT; // received interface index + end; + {$NODEFINE TIn6PktInfo} + TIn6PktInfo = IN6_PKTINFO; + {$NODEFINE PIn6PktInfo} + PIn6PktInfo = ^TIn6PktInfo; +{$ENDIF} + +// Error codes from getaddrinfo() +const + {$EXTERNALSYM EAI_AGAIN} + EAI_AGAIN = WSATRY_AGAIN; + {$EXTERNALSYM EAI_BADFLAGS} + EAI_BADFLAGS = WSAEINVAL; + {$EXTERNALSYM EAI_FAIL} + EAI_FAIL = WSANO_RECOVERY; + {$EXTERNALSYM EAI_FAMILY} + EAI_FAMILY = WSAEAFNOSUPPORT; + {$EXTERNALSYM EAI_MEMORY} + EAI_MEMORY = WSA_NOT_ENOUGH_MEMORY; +// {$EXTERNALSYM EAI_NODATA} +// EAI_NODATA = WSANO_DATA; + {$EXTERNALSYM EAI_NONAME} + EAI_NONAME = WSAHOST_NOT_FOUND; + {$EXTERNALSYM EAI_SERVICE} + EAI_SERVICE = WSATYPE_NOT_FOUND; + {$EXTERNALSYM EAI_SOCKTYPE} + EAI_SOCKTYPE = WSAESOCKTNOSUPPORT; + +// DCR_FIX: EAI_NODATA remove or fix +// +// EAI_NODATA was removed from rfc2553bis +// need to find out from the authors why and +// determine the error for "no records of this type" +// temporarily, we'll keep #define to avoid changing +// code that could change back; use NONAME + {$EXTERNALSYM EAI_NODATA} + EAI_NODATA = EAI_NONAME; + +// Structure used in getaddrinfo() call +type + {$NODEFINE PAddrInfo} + PAddrInfo = ^ADDRINFO; + {$NODEFINE PPaddrinfo} + PPaddrinfo = ^PAddrInfo; + {$NODEFINE PPaddrinfoW} + PPaddrinfoW = ^PAddrInfoW; + {$EXTERNALSYM ADDRINFO} + ADDRINFO = record + ai_flags : Integer; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST + ai_family : Integer; // PF_xxx + ai_socktype : Integer; // SOCK_xxx + ai_protocol : Integer; // 0 or IPPROTO_xxx for IPv4 and IPv6 + ai_addrlen : size_t; // Length of ai_addr + ai_canonname : PAnsiChar; // Canonical name for nodename + ai_addr : PSOCKADDR; // Binary address + ai_next : PAddrInfo; // Next structure in linked list + end; + {$NODEFINE TAddrInfo} + TAddrInfo = ADDRINFO; + {$EXTERNALSYM LPADDRINFO} + LPADDRINFO = PAddrInfo; + + {$NODEFINE PAddrInfoW} + PAddrInfoW = ^ADDRINFOW; + {$EXTERNALSYM ADDRINFOW} + ADDRINFOW = record + ai_flags : Integer; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST + ai_family : Integer; // PF_xxx + ai_socktype : Integer; // SOCK_xxx + ai_protocol : Integer; // 0 or IPPROTO_xxx for IPv4 and IPv6 + ai_addrlen : size_t; // Length of ai_addr + ai_canonname : PWideChar; // Canonical name for nodename + ai_addr : PSOCKADDR; // Binary address + ai_next : PAddrInfoW; // Next structure in linked list + end; + {$NODEFINE TAddrInfoW} + TAddrInfoW = ADDRINFOW; + {$EXTERNALSYM LPADDRINFOW} + LPADDRINFOW = PAddrInfoW; + +// Flags used in "hints" argument to getaddrinfo() +const + {$EXTERNALSYM AI_PASSIVE} + AI_PASSIVE = $00000001; // Socket address will be used in bind() call + {$EXTERNALSYM AI_CANONNAME} + AI_CANONNAME = $00000002; // Return canonical name in first ai_canonname + {$EXTERNALSYM AI_NUMERICHOST} + AI_NUMERICHOST = $00000004; // Nodename must be a numeric address string + {$EXTERNALSYM AI_NUMERICSERV} + AI_NUMERICSERV = $00000008; // Servicename must be a numeric port number + {$EXTERNALSYM AI_ALL} + AI_ALL = $00000100; // Query both IP6 and IP4 with AI_V4MAPPED + {$EXTERNALSYM AI_ADDRCONFIG} + AI_ADDRCONFIG = $00000400; // Resolution only if global address configured + {$EXTERNALSYM AI_V4MAPPED} + AI_V4MAPPED = $00000800; // On v6 failure, query v4 and convert to V4MAPPED format (Vista or later) + {$EXTERNALSYM AI_NON_AUTHORITATIVE} + AI_NON_AUTHORITATIVE = $00004000; // LUP_NON_AUTHORITATIVE (Vista or later) + {$EXTERNALSYM AI_SECURE} + AI_SECURE = $00008000; // LUP_SECURE (Vista or later and applies only to NS_EMAIL namespace.) + {$EXTERNALSYM AI_RETURN_PREFERRED_NAMES} + AI_RETURN_PREFERRED_NAMES = $00010000; // LUP_RETURN_PREFERRED_NAMES (Vista or later and applies only to NS_EMAIL namespace.) + {$EXTERNALSYM AI_FQDN} + AI_FQDN = $00020000; // Return the FQDN in ai_canonname (Winapi.Windows 7 or later) + {$EXTERNALSYM AI_FILESERVER} + AI_FILESERVER = $00040000; // Resolving fileserver name resolution (Winapi.Windows 7 or later) + + +type + {$EXTERNALSYM PADDRINFOEXA} + PADDRINFOEXA = ^TAddrInfoEXA; + {$EXTERNALSYM ADDRINFOEXA} + ADDRINFOEXA = record + ai_flags : Integer; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST + ai_family : Integer; // PF_xxx + ai_socktype : Integer; // SOCK_xxx + ai_protocol : Integer; // 0 or IPPROTO_xxx for IPv4 and IPv6 + ai_addrlen : size_t; // Length of ai_addr + ai_canonname : PAnsiChar; // Canonical name for nodename + ai_addr : Psockaddr; // Binary address + ai_blob : Pointer; + ai_bloblen : size_t; + ai_provider : LPGUID; + ai_next : PADDRINFOEXA; // Next structure in linked list + end; + {$NODEFINE TAddrInfoEXA} + TAddrInfoExA = ADDRINFOEXA; + {$EXTERNALSYM LPADDRINFOEXA} + LPADDRINFOEXA = PADDRINFOEXA; + + {$EXTERNALSYM PADDRINFOEXW} + PADDRINFOEXW = ^TAddrInfoEXW; + {$EXTERNALSYM ADDRINFOEXW} + ADDRINFOEXW = record + ai_flags : Integer; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST + ai_family : Integer; // PF_xxx + ai_socktype : Integer; // SOCK_xxx + ai_protocol : Integer; // 0 or IPPROTO_xxx for IPv4 and IPv6 + ai_addrlen : size_t; // Length of ai_addr + ai_canonname : PWideChar; // Canonical name for nodename + ai_addr : Psockaddr; // Binary address + ai_blob : Pointer; + ai_bloblen : size_t; + ai_provider : LPGUID; + ai_next : PADDRINFOEXW; // Next structure in linked list + end; + {$NODEFINE TAddrInfoExW} + TAddrInfoExW = ADDRINFOEXA; + {$EXTERNALSYM LPADDRINFOEXW} + LPADDRINFOEXW = PADDRINFOEXW; + +var + {$EXTERNALSYM in6addr_any} + in6addr_any: TIn6Addr; + {$EXTERNALSYM in6addr_loopback} + in6addr_loopback: TIn6Addr; + +//============================================================= + +{ + wsipx.h + + Microsoft Winapi.Windows + Copyright (C) Microsoft Corporation, 1992-1999. + + Winapi.Windows Sockets include file for IPX/SPX. This file contains all + standardized IPX/SPX information. Include this header file after + winsock.h. + + To open an IPX socket, call socket() with an address family of + AF_IPX, a socket type of SOCK_DGRAM, and protocol NSPROTO_IPX. + Note that the protocol value must be specified, it cannot be 0. + All IPX packets are sent with the packet type field of the IPX + header set to 0. + + To open an SPX or SPXII socket, call socket() with an address + family of AF_IPX, socket type of SOCK_SEQPACKET or SOCK_STREAM, + and protocol of NSPROTO_SPX or NSPROTO_SPXII. If SOCK_SEQPACKET + is specified, then the end of message bit is respected, and + recv() calls are not completed until a packet is received with + the end of message bit set. If SOCK_STREAM is specified, then + the end of message bit is not respected, and recv() completes + as soon as any data is received, regardless of the setting of the + end of message bit. Send coalescing is never performed, and sends + smaller than a single packet are always sent with the end of + message bit set. Sends larger than a single packet are packetized + with the end of message bit set on only the last packet of the + send. +} + +// This is the structure of the SOCKADDR structure for IPX and SPX. +type + {$EXTERNALSYM SOCKADDR_IPX} + SOCKADDR_IPX = record + sa_family : u_short; + sa_netnum : Array [0..3] of AnsiChar; + sa_nodenum : Array [0..5] of AnsiChar; + sa_socket : u_short; + end; + {$NODEFINE TSockAddrIPX} + TSockAddrIPX = SOCKADDR_IPX; + {$NODEFINE PSockAddrIPX} + PSockAddrIPX = ^TSockAddrIPX; + {$EXTERNALSYM PSOCKADDR_IPX} + PSOCKADDR_IPX = PSockAddrIPX; + {$EXTERNALSYM LPSOCKADDR_IPX} + LPSOCKADDR_IPX = PSOCKADDR_IPX; + +// Protocol families used in the "protocol" parameter of the socket() API. +const + {$EXTERNALSYM NSPROTO_IPX} + NSPROTO_IPX = 1000; + {$EXTERNALSYM NSPROTO_SPX} + NSPROTO_SPX = 1256; + {$EXTERNALSYM NSPROTO_SPXII} + NSPROTO_SPXII = 1257; + + +//============================================================= + +{ + wsnwlink.h + + Microsoft Winapi.Windows + Copyright (C) Microsoft Corporation, 1992-1999. + Microsoft-specific extensions to the Winapi.Windows NT IPX/SPX Winapi.Windows + Sockets interface. These extensions are provided for use as + necessary for compatibility with existing applications. They are + otherwise not recommended for use, as they are only guaranteed to + work over the Microsoft IPX/SPX stack. An application which + uses these extensions may not work over other IPX/SPX + implementations. Include this header file after winsock.h and + wsipx.h. + + To open an IPX socket where a particular packet type is sent in + the IPX header, specify NSPROTO_IPX + n as the protocol parameter + of the socket() API. For example, to open an IPX socket that + sets the packet type to 34, use the following socket() call: + + s = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX + 34); +} + +// Below are socket option that may be set or retrieved by specifying +// the appropriate manifest in the "optname" parameter of getsockopt() +// or setsockopt(). Use NSPROTO_IPX as the "level" argument for the +// call. +const + +// Set/get the IPX packet type. The value specified in the +// optval argument will be set as the packet type on every IPX +// packet sent from this socket. The optval parameter of +// getsockopt()/setsockopt() points to an int. + {$EXTERNALSYM IPX_PTYPE} + IPX_PTYPE = $4000; + +// Set/get the receive filter packet type. Only IPX packets with +// a packet type equal to the value specified in the optval +// argument will be returned; packets with a packet type that +// does not match are discarded. optval points to an int. + {$EXTERNALSYM IPX_FILTERPTYPE} + IPX_FILTERPTYPE = $4001; + +// Stop filtering on packet type set with IPX_FILTERPTYPE. + {$EXTERNALSYM IPX_STOPFILTERPTYPE} + IPX_STOPFILTERPTYPE = $4003; + +// Set/get the value of the datastream field in the SPX header on +// every packet sent. optval points to an int. + {$EXTERNALSYM IPX_DSTYPE} + IPX_DSTYPE = $4002; + +// Enable extended addressing. On sends, adds the element +// "unsigned char sa_ptype" to the SOCKADDR_IPX structure, +// making the total length 15 bytes. On receives, add both +// the sa_ptype and "unsigned char sa_flags" to the SOCKADDR_IPX +// structure, making the total length 16 bytes. The current +// bits defined in sa_flags are: +// 0x01 - the received frame was sent as a broadcast +// 0x02 - the received frame was sent from this machine +// optval points to a BOOL. + {$EXTERNALSYM IPX_EXTENDED_ADDRESS} + IPX_EXTENDED_ADDRESS = $4004; + +// Send protocol header up on all receive packets. optval points +// to a BOOL. + {$EXTERNALSYM IPX_RECVHDR} + IPX_RECVHDR = $4005; + +// Get the maximum data size that can be sent. Not valid with +// setsockopt(). optval points to an int where the value is +// returned. + {$EXTERNALSYM IPX_MAXSIZE} + IPX_MAXSIZE = $4006; + +// Query information about a specific adapter that IPX is bound +// to. In a system with n adapters they are numbered 0 through n-1. +// Callers can issue the IPX_MAX_ADAPTER_NUM getsockopt() to find +// out the number of adapters present, or call IPX_ADDRESS with +// increasing values of adapternum until it fails. Not valid +// with setsockopt(). optval points to an instance of the +// IPX_ADDRESS_DATA structure with the adapternum filled in. + {$EXTERNALSYM IPX_ADDRESS} + IPX_ADDRESS = $4007; + +type + {$EXTERNALSYM IPX_ADDRESS_DATA} + IPX_ADDRESS_DATA = record + adapternum : Integer; // input: 0-based adapter number + netnum : Array [0..3] of Byte; // output: IPX network number + nodenum : Array [0..5] of Byte; // output: IPX node address + wan : Boolean; // output: TRUE = adapter is on a wan link + status : Boolean; // output: TRUE = wan link is up (or adapter is not wan) + maxpkt : Integer; // output: max packet size, not including IPX header + linkspeed : ULONG; // output: link speed in 100 bytes/sec (i.e. 96 == 9600 bps) + end; + {$NODEFINE TIPXAddressData} + TIPXAddressData = IPX_ADDRESS_DATA; + {$NODEFINE PIPXAddressData} + PIPXAddressData = ^TIPXAddressData; + {$EXTERNALSYM PIPX_ADDRESS_DATA} + PIPX_ADDRESS_DATA = PIPXAddressData; + +const +// Query information about a specific IPX network number. If the +// network is in IPX's cache it will return the information directly, {Do not Localize} +// otherwise it will issue RIP requests to find it. Not valid with +// setsockopt(). optval points to an instance of the IPX_NETNUM_DATA +// structure with the netnum filled in. + {$EXTERNALSYM IPX_GETNETINFO} + IPX_GETNETINFO = $4008; + +type + {$EXTERNALSYM IPX_NETNUM_DATA} + IPX_NETNUM_DATA = record + netnum : Array [0..3] of Byte; // input: IPX network number + hopcount : Word; // output: hop count to this network, in machine order + netdelay : Word; // output: tick count to this network, in machine order + cardnum : Integer; // output: 0-based adapter number used to route to this net; + // can be used as adapternum input to IPX_ADDRESS + router : Array [0..5] of Byte; // output: MAC address of the next hop router, zeroed if + // the network is directly attached + end; + {$NODEFINE TIPXNetNumData} + TIPXNetNumData = IPX_NETNUM_DATA; + {$NODEFINE PIPXNetNumData} + PIPXNetNumData = ^TIPXNetNumData; + {$EXTERNALSYM PIPX_NETNUM_DATA} + PIPX_NETNUM_DATA = PIPXNetNumData; + +const +// Like IPX_GETNETINFO except it does not issue RIP requests. If the +// network is in IPX's cache it will return the information, otherwise {Do not Localize} +// it will fail (see also IPX_RERIPNETNUMBER which always forces a +// re-RIP). Not valid with setsockopt(). optval points to an instance of +// the IPX_NETNUM_DATA structure with the netnum filled in. + {$EXTERNALSYM IPX_GETNETINFO_NORIP} + IPX_GETNETINFO_NORIP = $4009; + +// Get information on a connected SPX socket. optval points +// to an instance of the IPX_SPXCONNSTATUS_DATA structure. +// *** All numbers are in Novell (high-low) order. *** + {$EXTERNALSYM IPX_SPXGETCONNECTIONSTATUS} + IPX_SPXGETCONNECTIONSTATUS = $400B; + +type + {$EXTERNALSYM IPX_SPXCONNSTATUS_DATA} + IPX_SPXCONNSTATUS_DATA = record + ConnectionState : Byte; + WatchDogActive : Byte; + LocalConnectionId : Word; + RemoteConnectionId : Word; + LocalSequenceNumber : Word; + LocalAckNumber : Word; + LocalAllocNumber : Word; + RemoteAckNumber : Word; + RemoteAllocNumber : Word; + LocalSocket : Word; + ImmediateAddress : Array [0..5] of Byte; + RemoteNetwork : Array [0..3] of Byte; + RemoteNode : Array [0..5] of Byte; + RemoteSocket : Word; + RetransmissionCount : Word; + EstimatedRoundTripDelay : Word; // In milliseconds + RetransmittedPackets : Word; + SuppressedPacket : Word; + end; + {$NODEFINE TIPXSPXConnStatusData} + TIPXSPXConnStatusData = IPX_SPXCONNSTATUS_DATA; + {$NODEFINE PIPXSPXConnStatusData} + PIPXSPXConnStatusData = ^TIPXSPXConnStatusData; + {$EXTERNALSYM PIPX_SPXCONNSTATUS_DATA} + PIPX_SPXCONNSTATUS_DATA = PIPXSPXConnStatusData; + +const +// Get notification when the status of an adapter that IPX is +// bound to changes. Typically this will happen when a wan line +// goes up or down. Not valid with setsockopt(). optval points +// to a buffer which contains an IPX_ADDRESS_DATA structure +// followed immediately by a HANDLE to an unsignaled event. +// +// When the getsockopt() query is submitted, it will complete +// successfully. However, the IPX_ADDRESS_DATA pointed to by +// optval will not be updated at that point. Instead the +// request is queued internally inside the transport. +// +// When the status of an adapter changes, IPX will locate a +// queued getsockopt() query and fill in all the fields in the +// IPX_ADDRESS_DATA structure. It will then signal the event +// pointed to by the HANDLE in the optval buffer. This handle +// should be obtained before calling getsockopt() by calling +// CreateEvent(). If multiple getsockopts() are submitted at +// once, different events must be used. +// +// The event is used because the call needs to be asynchronous +// but currently getsockopt() does not support this. +// +// WARNING: In the current implementation, the transport will +// only signal one queued query for each status change. Therefore +// only one service which uses this query should be running at +// once. + {$EXTERNALSYM IPX_ADDRESS_NOTIFY} + IPX_ADDRESS_NOTIFY = $400C; + +// Get the maximum number of adapters present. If this call returns +// n then the adapters are numbered 0 through n-1. Not valid +// with setsockopt(). optval points to an int where the value +// is returned. + {$EXTERNALSYM IPX_MAX_ADAPTER_NUM} + IPX_MAX_ADAPTER_NUM = $400D; + +// Like IPX_GETNETINFO except it forces IPX to re-RIP even if the +// network is in its cache (but not if it is directly attached to). +// Not valid with setsockopt(). optval points to an instance of +// the IPX_NETNUM_DATA structure with the netnum filled in. + {$EXTERNALSYM IPX_RERIPNETNUMBER} + IPX_RERIPNETNUMBER = $400E; + +// A hint that broadcast packets may be received. The default is +// TRUE. Applications that do not need to receive broadcast packets +// should set this sockopt to FALSE which may cause better system +// performance (note that it does not necessarily cause broadcasts +// to be filtered for the application). Not valid with getsockopt(). +// optval points to a BOOL. + {$EXTERNALSYM IPX_RECEIVE_BROADCAST} + IPX_RECEIVE_BROADCAST = $400F; + +// On SPX connections, don't delay before sending ack. Applications {Do not Localize} +// that do not tend to have back-and-forth traffic over SPX should +// set this; it will increase the number of acks sent but will remove +// delays in sending acks. optval points to a BOOL. + {$EXTERNALSYM IPX_IMMEDIATESPXACK} + IPX_IMMEDIATESPXACK = $4010; + + +//============================================================= + +// wsnetbs.h +// Copyright (c) 1994-1999, Microsoft Corp. All rights reserved. +// +// Winapi.Windows Sockets include file for NETBIOS. This file contains all +// standardized NETBIOS information. Include this header file after +// winsock.h. + +// To open a NetBIOS socket, call the socket() function as follows: +// +// s = socket( AF_NETBIOS, {SOCK_SEQPACKET|SOCK_DGRAM}, -Lana ); +// +// where Lana is the NetBIOS Lana number of interest. For example, to +// open a socket for Lana 2, specify -2 as the "protocol" parameter +// to the socket() function. + + +// This is the structure of the SOCKADDR structure for NETBIOS. + +const + {$EXTERNALSYM NETBIOS_NAME_LENGTH} + NETBIOS_NAME_LENGTH = 16; + +type + {$EXTERNALSYM SOCKADDR_NB} + SOCKADDR_NB = record + snb_family : Smallint; + snb_type : u_short; + snb_name : array[0..NETBIOS_NAME_LENGTH-1] of AnsiChar; + end; + {$NODEFINE TSockAddrNB} + TSockAddrNB = SOCKADDR_NB; + {$NODEFINE PSockAddrNB} + PSockAddrNB = ^TSockAddrNB; + {$EXTERNALSYM PSOCKADDR_NB} + PSOCKADDR_NB = PSockAddrNB; + {$EXTERNALSYM LPSOCKADDR_NB} + LPSOCKADDR_NB = PSOCKADDR_NB; + +// Bit values for the snb_type field of SOCKADDR_NB. +const + {$EXTERNALSYM NETBIOS_UNIQUE_NAME} + NETBIOS_UNIQUE_NAME = $0000; + {$EXTERNALSYM NETBIOS_GROUP_NAME} + NETBIOS_GROUP_NAME = $0001; + {$EXTERNALSYM NETBIOS_TYPE_QUICK_UNIQUE} + NETBIOS_TYPE_QUICK_UNIQUE = $0002; + {$EXTERNALSYM NETBIOS_TYPE_QUICK_GROUP} + NETBIOS_TYPE_QUICK_GROUP = $0003; + +// A macro convenient for setting up NETBIOS SOCKADDRs. +{$EXTERNALSYM SET_NETBIOS_SOCKADDR} +procedure SET_NETBIOS_SOCKADDR(snb : PSockAddrNB; const SnbType : Word; const Name : PAnsiChar; const Port : AnsiChar); + + +//============================================================= + +// Copyright 1997 - 1998 Microsoft Corporation +// +// Module Name: +// +// ws2atm.h +// +// Abstract: +// +// Winsock 2 ATM Annex definitions. + +const + {$EXTERNALSYM ATMPROTO_AALUSER} + ATMPROTO_AALUSER = $00; // User-defined AAL + {$EXTERNALSYM ATMPROTO_AAL1} + ATMPROTO_AAL1 = $01; // AAL 1 + {$EXTERNALSYM ATMPROTO_AAL2} + ATMPROTO_AAL2 = $02; // AAL 2 + {$EXTERNALSYM ATMPROTO_AAL34} + ATMPROTO_AAL34 = $03; // AAL 3/4 + {$EXTERNALSYM ATMPROTO_AAL5} + ATMPROTO_AAL5 = $05; // AAL 5 + + {$EXTERNALSYM SAP_FIELD_ABSENT} + SAP_FIELD_ABSENT = $FFFFFFFE; + {$EXTERNALSYM SAP_FIELD_ANY} + SAP_FIELD_ANY = $FFFFFFFF; + {$EXTERNALSYM SAP_FIELD_ANY_AESA_SEL} + SAP_FIELD_ANY_AESA_SEL = $FFFFFFFA; + {$EXTERNALSYM SAP_FIELD_ANY_AESA_REST} + SAP_FIELD_ANY_AESA_REST = $FFFFFFFB; + + // values used for AddressType in struct ATM_ADDRESS + {$EXTERNALSYM ATM_E164} + ATM_E164 = $01; // E.164 addressing scheme + {$EXTERNALSYM ATM_NSAP} + ATM_NSAP = $02; // NSAP-style ATM Endsystem Address scheme + {$EXTERNALSYM ATM_AESA} + ATM_AESA = $02; // NSAP-style ATM Endsystem Address scheme + + {$EXTERNALSYM ATM_ADDR_SIZE} + ATM_ADDR_SIZE = 20; + +type + {$EXTERNALSYM ATM_ADDRESS} + ATM_ADDRESS = record + AddressType : DWORD; // E.164 or NSAP-style ATM Endsystem Address + NumofDigits : DWORD; // number of digits; + Addr : Array[0..ATM_ADDR_SIZE-1] of Byte; // IA5 digits for E164, BCD encoding for NSAP + // format as defined in the ATM Forum UNI 3.1 + end; + +// values used for Layer2Protocol in B-LLI +const + {$EXTERNALSYM BLLI_L2_ISO_1745} + BLLI_L2_ISO_1745 = $01; // Basic mode ISO 1745 + {$EXTERNALSYM BLLI_L2_Q921} + BLLI_L2_Q921 = $02; // CCITT Rec. Q.921 + {$EXTERNALSYM BLLI_L2_X25L} + BLLI_L2_X25L = $06; // CCITT Rec. X.25, link layer + {$EXTERNALSYM BLLI_L2_X25M} + BLLI_L2_X25M = $07; // CCITT Rec. X.25, multilink + {$EXTERNALSYM BLLI_L2_ELAPB} + BLLI_L2_ELAPB = $08; // Extended LAPB; for half duplex operation + {$EXTERNALSYM BLLI_L2_HDLC_NRM} + BLLI_L2_HDLC_NRM = $09; // HDLC NRM (ISO 4335) + {$EXTERNALSYM BLLI_L2_HDLC_ABM} + BLLI_L2_HDLC_ABM = $0A; // HDLC ABM (ISO 4335) + {$EXTERNALSYM BLLI_L2_HDLC_ARM} + BLLI_L2_HDLC_ARM = $0B; // HDLC ARM (ISO 4335) + {$EXTERNALSYM BLLI_L2_LLC} + BLLI_L2_LLC = $0C; // LAN logical link control (ISO 8802/2) + {$EXTERNALSYM BLLI_L2_X75} + BLLI_L2_X75 = $0D; // CCITT Rec. X.75, single link procedure + {$EXTERNALSYM BLLI_L2_Q922} + BLLI_L2_Q922 = $0E; // CCITT Rec. Q.922 + {$EXTERNALSYM BLLI_L2_USER_SPECIFIED} + BLLI_L2_USER_SPECIFIED = $10; // User Specified + {$EXTERNALSYM BLLI_L2_ISO_7776} + BLLI_L2_ISO_7776 = $11; // ISO 7776 DTE-DTE operation + +// values used for Layer3Protocol in B-LLI + {$EXTERNALSYM BLLI_L3_X25} + BLLI_L3_X25 = $06; // CCITT Rec. X.25, packet layer + {$EXTERNALSYM BLLI_L3_ISO_8208} + BLLI_L3_ISO_8208 = $07; // ISO/IEC 8208 (X.25 packet layer for DTE + {$EXTERNALSYM BLLI_L3_X223} + BLLI_L3_X223 = $08; // X.223/ISO 8878 + {$EXTERNALSYM BLLI_L3_SIO_8473} + BLLI_L3_SIO_8473 = $09; // ISO/IEC 8473 (OSI connectionless) + {$EXTERNALSYM BLLI_L3_T70} + BLLI_L3_T70 = $0A; // CCITT Rec. T.70 min. network layer + {$EXTERNALSYM BLLI_L3_ISO_TR9577} + BLLI_L3_ISO_TR9577 = $0B; // ISO/IEC TR 9577 Network Layer Protocol ID + {$EXTERNALSYM BLLI_L3_USER_SPECIFIED} + BLLI_L3_USER_SPECIFIED = $10; // User Specified + +// values used for Layer3IPI in B-LLI + {$EXTERNALSYM BLLI_L3_IPI_SNAP} + BLLI_L3_IPI_SNAP = $80; // IEEE 802.1 SNAP identifier + {$EXTERNALSYM BLLI_L3_IPI_IP} + BLLI_L3_IPI_IP = $CC; // Internet Protocol (IP) identifier + +type + {$EXTERNALSYM ATM_BLLI} + ATM_BLLI = record + // Identifies the layer-two protocol. + // Corresponds to the User information layer 2 protocol field in the B-LLI information element. + // A value of SAP_FIELD_ABSENT indicates that this field is not used, and a value of SAP_FIELD_ANY means wildcard. + Layer2Protocol : DWORD; // User information layer 2 protocol + // Identifies the user-specified layer-two protocol. + // Only used if the Layer2Protocol parameter is set to BLLI_L2_USER_SPECIFIED. + // The valid values range from zero-127. + // Corresponds to the User specified layer 2 protocol information field in the B-LLI information element. + Layer2UserSpecifiedProtocol : DWORD; // User specified layer 2 protocol information + // Identifies the layer-three protocol. + // Corresponds to the User information layer 3 protocol field in the B-LLI information element. + // A value of SAP_FIELD_ABSENT indicates that this field is not used, and a value of SAP_FIELD_ANY means wildcard. + Layer3Protocol : DWORD; // User information layer 3 protocol + // Identifies the user-specified layer-three protocol. + // Only used if the Layer3Protocol parameter is set to BLLI_L3_USER_SPECIFIED. + // The valid values range from zero-127. + // Corresponds to the User specified layer 3 protocol information field in the B-LLI information element. + Layer3UserSpecifiedProtocol : DWORD; // User specified layer 3 protocol information + // Identifies the layer-three Initial Protocol Identifier. + // Only used if the Layer3Protocol parameter is set to BLLI_L3_ISO_TR9577. + // Corresponds to the ISO/IEC TR 9577 Initial Protocol Identifier field in the B-LLI information element. + Layer3IPI : DWORD; // ISO/IEC TR 9577 Initial Protocol Identifier + // Identifies the 802.1 SNAP identifier. + // Only used if the Layer3Protocol parameter is set to BLLI_L3_ISO_TR9577 and Layer3IPI is set to BLLI_L3_IPI_SNAP, + // indicating an IEEE 802.1 SNAP identifier. Corresponds to the OUI and PID fields in the B-LLI information element. + SnapID : Array[0..4] of Byte; // SNAP ID consisting of OUI and PID + end; + +// values used for the HighLayerInfoType field in ATM_BHLI +const + {$EXTERNALSYM BHLI_ISO} + BHLI_ISO = $00; // ISO + {$EXTERNALSYM BHLI_UserSpecific} + BHLI_UserSpecific = $01; // User Specific + {$EXTERNALSYM BHLI_HighLayerProfile} + BHLI_HighLayerProfile = $02; // High layer profile (only in UNI3.0) + {$EXTERNALSYM BHLI_VendorSpecificAppId} + BHLI_VendorSpecificAppId = $03; // Vendor-Specific Application ID + +type + {$EXTERNALSYM ATM_BHLI} + ATM_BHLI = record + // Identifies the high layer information type field in the B-LLI information element. + // Note that the type BHLI_HighLayerProfile has been eliminated in UNI 3.1. + // A value of SAP_FIELD_ABSENT indicates that B-HLI is not present, and a value of SAP_FIELD_ANY means wildcard. + HighLayerInfoType : DWORD; // High Layer Information Type + // Identifies the number of bytes from one to eight in the HighLayerInfo array. + // Valid values include eight for the cases of BHLI_ISO and BHLI_UserSpecific, + // four for BHLI_HighLayerProfile, and seven for BHLI_VendorSpecificAppId. + HighLayerInfoLength : DWORD; // number of bytes in HighLayerInfo + // Identifies the high layer information field in the B-LLI information element. + // In the case of HighLayerInfoType being BHLI_VendorSpecificAppId, + // the first 3 bytes consist of a globally-administered Organizationally Unique Identifier (OUI) + // (as per IEEE standard 802-1990), followed by a 4-byte application identifier, + // which is administered by the vendor identified by the OUI. + // Value for the case of BHLI_UserSpecific is user defined and requires bilateral agreement between two end users. + HighLayerInfo : Array[0..7] of Byte; // the value dependent on the HighLayerInfoType field + end; + +// A new address family, AF_ATM, is introduced for native ATM services, +// and the corresponding SOCKADDR structure, sockaddr_atm, is defined in the following. +// To open a socket for native ATM services, parameters in socket should contain +// AF_ATM, SOCK_RAW, and ATMPROTO_AAL5 or ATMPROTO_AALUSER, respectively. + {$EXTERNALSYM SOCKADDR_ATM} + SOCKADDR_ATM = record + // Identifies the address family, which is AF_ATM in this case. + satm_family : u_short; + // Identifies the ATM address that could be either in E.164 or NSAP-style ATM End Systems Address format. + // This field will be mapped to the Called Party Number IE (Information Element) + // if it is specified in bind and WSPBind for a listening socket, or in connect, WSAConnect, WSPConnect, + // WSAJoinLeaf, or WSPJoinLeaf for a connecting socket. + // It will be mapped to the Calling Party Number IE if specified in bind and WSPBind for a connecting socket. + satm_number : ATM_ADDRESS; + // Identifies the fields in the B-LLI Information Element that are used along with satm_bhli to identify an application. + // Note that the B-LLI layer two information is treated as not present + // if its Layer2Protocol field contains SAP_FIELD_ABSENT, or as a wildcard if it contains SAP_FIELD_ANY. + // Similarly, the B-LLI layer three information is treated as not present + // if its Layer3Protocol field contains SAP_FIELD_ABSENT, or as a wildcard if it contains SAP_FIELD_ANY. + satm_blli : ATM_BLLI; // B-LLI + // Identifies the fields in the B-HLI Information Element that are used along with satm_blli to identify an application. + satm_bhli : ATM_BHLI; // B-HLI + end; + {$NODEFINE TSockAddrATM} + TSockAddrATM = SOCKADDR_ATM; + {$NODEFINE PSockAddrATM} + PSockAddrATM = ^TSockAddrATM; + {$EXTERNALSYM PSOCKADDR_ATM} + PSOCKADDR_ATM = PSockAddrATM; + {$EXTERNALSYM LPSOCKADDR_ATM} + LPSOCKADDR_ATM = PSOCKADDR_ATM; + + {$EXTERNALSYM Q2931_IE_TYPE} + Q2931_IE_TYPE = (IE_AALParameters, IE_TrafficDescriptor, + IE_BroadbandBearerCapability, IE_BHLI, IE_BLLI,IE_CalledPartyNumber, + IE_CalledPartySubaddress, IE_CallingPartyNumber, IE_CallingPartySubaddress, + IE_Cause, IE_QOSClass, IE_TransitNetworkSelection + ); + + {$EXTERNALSYM Q2931_IE} + Q2931_IE = record + IEType : Q2931_IE_TYPE; + IELength : ULONG; + IE : Array[0..0] of Byte; + end; + +// manifest constants for the AALType field in struct AAL_PARAMETERS_IE + {$EXTERNALSYM AAL_TYPE} + AAL_TYPE = LongInt; + +const + {$EXTERNALSYM AALTYPE_5} + AALTYPE_5 = 5; // AAL 5 + {$EXTERNALSYM AALTYPE_USER} + AALTYPE_USER = 16; // user-defined AAL + + // values used for the Mode field in struct AAL5_PARAMETERS + {$EXTERNALSYM AAL5_MODE_MESSAGE} + AAL5_MODE_MESSAGE = $01; + {$EXTERNALSYM AAL5_MODE_STREAMING} + AAL5_MODE_STREAMING = $02; + +// values used for the SSCSType field in struct AAL5_PARAMETERS + {$EXTERNALSYM AAL5_SSCS_NULL} + AAL5_SSCS_NULL = $00; + {$EXTERNALSYM AAL5_SSCS_SSCOP_ASSURED} + AAL5_SSCS_SSCOP_ASSURED = $01; + {$EXTERNALSYM AAL5_SSCS_SSCOP_NON_ASSURED} + AAL5_SSCS_SSCOP_NON_ASSURED = $02; + {$EXTERNALSYM AAL5_SSCS_FRAME_RELAY} + AAL5_SSCS_FRAME_RELAY = $04; + +type + {$EXTERNALSYM AAL5_PARAMETERS} + AAL5_PARAMETERS = record + ForwardMaxCPCSSDUSize : ULONG; + BackwardMaxCPCSSDUSize : ULONG; + Mode : Byte; // only available in UNI 3.0 + SSCSType : Byte; + end; + + {$EXTERNALSYM AALUSER_PARAMETERS} + AALUSER_PARAMETERS = record + UserDefined : ULONG; + end; + + {$EXTERNALSYM AAL_PARAMETERS_IE} + AAL_PARAMETERS_IE = record + AALType : AAL_TYPE; + case Byte of + 0: ( AAL5Parameters : AAL5_PARAMETERS ); + 1: ( AALUserParameters : AALUSER_PARAMETERS ); + end; + + {$EXTERNALSYM ATM_TD} + ATM_TD = record + PeakCellRate_CLP0 : ULONG; + PeakCellRate_CLP01 : ULONG; + SustainableCellRate_CLP0 : ULONG; + SustainableCellRate_CLP01 : ULONG; + MaxBurstSize_CLP0 : ULONG; + MaxBurstSize_CLP01 : ULONG; + Tagging : LongBool; + end; + + {$EXTERNALSYM ATM_TRAFFIC_DESCRIPTOR_IE} + ATM_TRAFFIC_DESCRIPTOR_IE = record + _Forward : ATM_TD; + Backward : ATM_TD; + BestEffort : LongBool; + end; + +// values used for the BearerClass field in struct ATM_BROADBAND_BEARER_CAPABILITY_IE +const + {$EXTERNALSYM BCOB_A} + BCOB_A = $01; // Bearer class A + {$EXTERNALSYM BCOB_C} + BCOB_C = $03; // Bearer class C + {$EXTERNALSYM BCOB_X} + BCOB_X = $10; // Bearer class X + +// values used for the TrafficType field in struct ATM_BROADBAND_BEARER_CAPABILITY_IE + {$EXTERNALSYM TT_NOIND} + TT_NOIND = $00; // No indication of traffic type + {$EXTERNALSYM TT_CBR} + TT_CBR = $04; // Constant bit rate + {$EXTERNALSYM TT_VBR} + TT_VBR = $06; // Variable bit rate + +// values used for the TimingRequirements field in struct ATM_BROADBAND_BEARER_CAPABILITY_IE + {$EXTERNALSYM TR_NOIND} + TR_NOIND = $00; // No timing requirement indication + {$EXTERNALSYM TR_END_TO_END} + TR_END_TO_END = $01; // End-to-end timing required + {$EXTERNALSYM TR_NO_END_TO_END} + TR_NO_END_TO_END = $02; // End-to-end timing not required + +// values used for the ClippingSusceptability field in struct ATM_BROADBAND_BEARER_CAPABILITY_IE + {$EXTERNALSYM CLIP_NOT} + CLIP_NOT = $00; // Not susceptible to clipping + {$EXTERNALSYM CLIP_SUS} + CLIP_SUS = $20; // Susceptible to clipping + +// values used for the UserPlaneConnectionConfig field in struct ATM_BROADBAND_BEARER_CAPABILITY_IE + {$EXTERNALSYM UP_P2P} + UP_P2P = $00; // Point-to-point connection + {$EXTERNALSYM UP_P2MP} + UP_P2MP = $01; // Point-to-multipoint connection + +type + {$EXTERNALSYM ATM_BROADBAND_BEARER_CAPABILITY_IE} + ATM_BROADBAND_BEARER_CAPABILITY_IE = record + BearerClass : Byte; + TrafficType : Byte; + TimingRequirements : Byte; + ClippingSusceptability : Byte; + UserPlaneConnectionConfig : Byte; + end; + {$EXTERNALSYM ATM_BHLI_IE} + ATM_BHLI_IE = ATM_BHLI; + +// values used for the Layer2Mode field in struct ATM_BLLI_IE +const + {$EXTERNALSYM BLLI_L2_MODE_NORMAL} + BLLI_L2_MODE_NORMAL = $40; + {$EXTERNALSYM BLLI_L2_MODE_EXT} + BLLI_L2_MODE_EXT = $80; + +// values used for the Layer3Mode field in struct ATM_BLLI_IE + {$EXTERNALSYM BLLI_L3_MODE_NORMAL} + BLLI_L3_MODE_NORMAL = $40; + {$EXTERNALSYM BLLI_L3_MODE_EXT} + BLLI_L3_MODE_EXT = $80; + +// values used for the Layer3DefaultPacketSize field in struct ATM_BLLI_IE + {$EXTERNALSYM BLLI_L3_PACKET_16} + BLLI_L3_PACKET_16 = $04; + {$EXTERNALSYM BLLI_L3_PACKET_32} + BLLI_L3_PACKET_32 = $05; + {$EXTERNALSYM BLLI_L3_PACKET_64} + BLLI_L3_PACKET_64 = $06; + {$EXTERNALSYM BLLI_L3_PACKET_128} + BLLI_L3_PACKET_128 = $07; + {$EXTERNALSYM BLLI_L3_PACKET_256} + BLLI_L3_PACKET_256 = $08; + {$EXTERNALSYM BLLI_L3_PACKET_512} + BLLI_L3_PACKET_512 = $09; + {$EXTERNALSYM BLLI_L3_PACKET_1024} + BLLI_L3_PACKET_1024 = $0A; + {$EXTERNALSYM BLLI_L3_PACKET_2048} + BLLI_L3_PACKET_2048 = $0B; + {$EXTERNALSYM BLLI_L3_PACKET_4096} + BLLI_L3_PACKET_4096 = $0C; + +type + {$EXTERNALSYM ATM_BLLI_IE} + ATM_BLLI_IE = record + Layer2Protocol : DWORD; // User information layer 2 protocol + Layer2Mode : Byte; + Layer2WindowSize : Byte; + Layer2UserSpecifiedProtocol : DWORD; // User specified layer 2 protocol information + Layer3Protocol : DWORD; // User information layer 3 protocol + Layer3Mode : Byte; + Layer3DefaultPacketSize : Byte; + Layer3PacketWindowSize : Byte; + Layer3UserSpecifiedProtocol : DWORD; // User specified layer 3 protocol information + Layer3IPI : DWORD; // ISO/IEC TR 9577 Initial Protocol Identifier + SnapID : Array[0..4] of Byte; // SNAP ID consisting of OUI and PID + end; + {$EXTERNALSYM ATM_CALLED_PARTY_NUMBER_IE} + ATM_CALLED_PARTY_NUMBER_IE = ATM_ADDRESS; + {$EXTERNALSYM ATM_CALLED_PARTY_SUBADDRESS_IE} + ATM_CALLED_PARTY_SUBADDRESS_IE = ATM_ADDRESS; + +// values used for the Presentation_Indication field in struct ATM_CALLING_PARTY_NUMBER_IE +const + {$EXTERNALSYM PI_ALLOWED} + PI_ALLOWED = $00; + {$EXTERNALSYM PI_RESTRICTED} + PI_RESTRICTED = $40; + {$EXTERNALSYM PI_NUMBER_NOT_AVAILABLE} + PI_NUMBER_NOT_AVAILABLE = $80; + +// values used for the Screening_Indicator field in struct ATM_CALLING_PARTY_NUMBER_IE + {$EXTERNALSYM SI_USER_NOT_SCREENED} + SI_USER_NOT_SCREENED = $00; + {$EXTERNALSYM SI_USER_PASSED} + SI_USER_PASSED = $01; + {$EXTERNALSYM SI_USER_FAILED} + SI_USER_FAILED = $02; + {$EXTERNALSYM SI_NETWORK} + SI_NETWORK = $03; + +type + {$EXTERNALSYM ATM_CALLING_PARTY_NUMBER_IE} + ATM_CALLING_PARTY_NUMBER_IE = record + ATM_Number : ATM_ADDRESS; + Presentation_Indication : Byte; + Screening_Indicator : Byte; + end; + {$EXTERNALSYM ATM_CALLING_PARTY_SUBADDRESS_IE} + ATM_CALLING_PARTY_SUBADDRESS_IE = ATM_ADDRESS; + +// values used for the Location field in struct ATM_CAUSE_IE +const + {$EXTERNALSYM CAUSE_LOC_USER} + CAUSE_LOC_USER = $00; + {$EXTERNALSYM CAUSE_LOC_PRIVATE_LOCAL} + CAUSE_LOC_PRIVATE_LOCAL = $01; + {$EXTERNALSYM CAUSE_LOC_PUBLIC_LOCAL} + CAUSE_LOC_PUBLIC_LOCAL = $02; + {$EXTERNALSYM CAUSE_LOC_TRANSIT_NETWORK} + CAUSE_LOC_TRANSIT_NETWORK = $03; + {$EXTERNALSYM CAUSE_LOC_PUBLIC_REMOTE} + CAUSE_LOC_PUBLIC_REMOTE = $04; + {$EXTERNALSYM CAUSE_LOC_PRIVATE_REMOTE} + CAUSE_LOC_PRIVATE_REMOTE = $05; + {$EXTERNALSYM CAUSE_LOC_INTERNATIONAL_NETWORK} + CAUSE_LOC_INTERNATIONAL_NETWORK = $06; + {$EXTERNALSYM CAUSE_LOC_BEYOND_INTERWORKING} + CAUSE_LOC_BEYOND_INTERWORKING = $0A; + +// values used for the Cause field in struct ATM_CAUSE_IE + {$EXTERNALSYM CAUSE_UNALLOCATED_NUMBER} + CAUSE_UNALLOCATED_NUMBER = $01; + {$EXTERNALSYM CAUSE_NO_ROUTE_TO_TRANSIT_NETWORK} + CAUSE_NO_ROUTE_TO_TRANSIT_NETWORK = $02; + {$EXTERNALSYM CAUSE_NO_ROUTE_TO_DESTINATION} + CAUSE_NO_ROUTE_TO_DESTINATION = $03; + {$EXTERNALSYM CAUSE_VPI_VCI_UNACCEPTABLE} + CAUSE_VPI_VCI_UNACCEPTABLE = $0A; + {$EXTERNALSYM CAUSE_NORMAL_CALL_CLEARING} + CAUSE_NORMAL_CALL_CLEARING = $10; + {$EXTERNALSYM CAUSE_USER_BUSY} + CAUSE_USER_BUSY = $11; + {$EXTERNALSYM CAUSE_NO_USER_RESPONDING} + CAUSE_NO_USER_RESPONDING = $12; + {$EXTERNALSYM CAUSE_CALL_REJECTED} + CAUSE_CALL_REJECTED = $15; + {$EXTERNALSYM CAUSE_NUMBER_CHANGED} + CAUSE_NUMBER_CHANGED = $16; + {$EXTERNALSYM CAUSE_USER_REJECTS_CLIR} + CAUSE_USER_REJECTS_CLIR = $17; + {$EXTERNALSYM CAUSE_DESTINATION_OUT_OF_ORDER} + CAUSE_DESTINATION_OUT_OF_ORDER = $1B; + {$EXTERNALSYM CAUSE_INVALID_NUMBER_FORMAT} + CAUSE_INVALID_NUMBER_FORMAT = $1C; + {$EXTERNALSYM CAUSE_STATUS_ENQUIRY_RESPONSE} + CAUSE_STATUS_ENQUIRY_RESPONSE = $1E; + {$EXTERNALSYM CAUSE_NORMAL_UNSPECIFIED} + CAUSE_NORMAL_UNSPECIFIED = $1F; + {$EXTERNALSYM CAUSE_VPI_VCI_UNAVAILABLE} + CAUSE_VPI_VCI_UNAVAILABLE = $23; + {$EXTERNALSYM CAUSE_NETWORK_OUT_OF_ORDER} + CAUSE_NETWORK_OUT_OF_ORDER = $26; + {$EXTERNALSYM CAUSE_TEMPORARY_FAILURE} + CAUSE_TEMPORARY_FAILURE = $29; + {$EXTERNALSYM CAUSE_ACCESS_INFORMAION_DISCARDED} + CAUSE_ACCESS_INFORMAION_DISCARDED = $2B; + {$EXTERNALSYM CAUSE_NO_VPI_VCI_AVAILABLE} + CAUSE_NO_VPI_VCI_AVAILABLE = $2D; + {$EXTERNALSYM CAUSE_RESOURCE_UNAVAILABLE} + CAUSE_RESOURCE_UNAVAILABLE = $2F; + {$EXTERNALSYM CAUSE_QOS_UNAVAILABLE} + CAUSE_QOS_UNAVAILABLE = $31; + {$EXTERNALSYM CAUSE_USER_CELL_RATE_UNAVAILABLE} + CAUSE_USER_CELL_RATE_UNAVAILABLE = $33; + {$EXTERNALSYM CAUSE_BEARER_CAPABILITY_UNAUTHORIZED} + CAUSE_BEARER_CAPABILITY_UNAUTHORIZED = $39; + {$EXTERNALSYM CAUSE_BEARER_CAPABILITY_UNAVAILABLE} + CAUSE_BEARER_CAPABILITY_UNAVAILABLE = $3A; + {$EXTERNALSYM CAUSE_OPTION_UNAVAILABLE} + CAUSE_OPTION_UNAVAILABLE = $3F; + {$EXTERNALSYM CAUSE_BEARER_CAPABILITY_UNIMPLEMENTED} + CAUSE_BEARER_CAPABILITY_UNIMPLEMENTED = $41; + {$EXTERNALSYM CAUSE_UNSUPPORTED_TRAFFIC_PARAMETERS} + CAUSE_UNSUPPORTED_TRAFFIC_PARAMETERS = $49; + {$EXTERNALSYM CAUSE_INVALID_CALL_REFERENCE} + CAUSE_INVALID_CALL_REFERENCE = $51; + {$EXTERNALSYM CAUSE_CHANNEL_NONEXISTENT} + CAUSE_CHANNEL_NONEXISTENT = $52; + {$EXTERNALSYM CAUSE_INCOMPATIBLE_DESTINATION} + CAUSE_INCOMPATIBLE_DESTINATION = $58; + {$EXTERNALSYM CAUSE_INVALID_ENDPOINT_REFERENCE} + CAUSE_INVALID_ENDPOINT_REFERENCE = $59; + {$EXTERNALSYM CAUSE_INVALID_TRANSIT_NETWORK_SELECTION} + CAUSE_INVALID_TRANSIT_NETWORK_SELECTION = $5B; + {$EXTERNALSYM CAUSE_TOO_MANY_PENDING_ADD_PARTY} + CAUSE_TOO_MANY_PENDING_ADD_PARTY = $5C; + {$EXTERNALSYM CAUSE_AAL_PARAMETERS_UNSUPPORTED} + CAUSE_AAL_PARAMETERS_UNSUPPORTED = $5D; + {$EXTERNALSYM CAUSE_MANDATORY_IE_MISSING} + CAUSE_MANDATORY_IE_MISSING = $60; + {$EXTERNALSYM CAUSE_UNIMPLEMENTED_MESSAGE_TYPE} + CAUSE_UNIMPLEMENTED_MESSAGE_TYPE = $61; + {$EXTERNALSYM CAUSE_UNIMPLEMENTED_IE} + CAUSE_UNIMPLEMENTED_IE = $63; + {$EXTERNALSYM CAUSE_INVALID_IE_CONTENTS} + CAUSE_INVALID_IE_CONTENTS = $64; + {$EXTERNALSYM CAUSE_INVALID_STATE_FOR_MESSAGE} + CAUSE_INVALID_STATE_FOR_MESSAGE = $65; + {$EXTERNALSYM CAUSE_RECOVERY_ON_TIMEOUT} + CAUSE_RECOVERY_ON_TIMEOUT = $66; + {$EXTERNALSYM CAUSE_INCORRECT_MESSAGE_LENGTH} + CAUSE_INCORRECT_MESSAGE_LENGTH = $68; + {$EXTERNALSYM CAUSE_PROTOCOL_ERROR} + CAUSE_PROTOCOL_ERROR = $6F; + +// values used for the Condition portion of the Diagnostics field +// in struct ATM_CAUSE_IE, for certain Cause values + {$EXTERNALSYM CAUSE_COND_UNKNOWN} + CAUSE_COND_UNKNOWN = $00; + {$EXTERNALSYM CAUSE_COND_PERMANENT} + CAUSE_COND_PERMANENT = $01; + {$EXTERNALSYM CAUSE_COND_TRANSIENT} + CAUSE_COND_TRANSIENT = $02; + +// values used for the Rejection Reason portion of the Diagnostics field +// in struct ATM_CAUSE_IE, for certain Cause values + {$EXTERNALSYM CAUSE_REASON_USER} + CAUSE_REASON_USER = $00; + {$EXTERNALSYM CAUSE_REASON_IE_MISSING} + CAUSE_REASON_IE_MISSING = $04; + {$EXTERNALSYM CAUSE_REASON_IE_INSUFFICIENT} + CAUSE_REASON_IE_INSUFFICIENT = $08; + +// values used for the P-U flag of the Diagnostics field +// in struct ATM_CAUSE_IE, for certain Cause values + {$EXTERNALSYM CAUSE_PU_PROVIDER} + CAUSE_PU_PROVIDER = $00; + {$EXTERNALSYM CAUSE_PU_USER} + CAUSE_PU_USER = $08; + +// values used for the N-A flag of the Diagnostics field +// in struct ATM_CAUSE_IE, for certain Cause values + {$EXTERNALSYM CAUSE_NA_NORMAL} + CAUSE_NA_NORMAL = $00; + {$EXTERNALSYM CAUSE_NA_ABNORMAL} + CAUSE_NA_ABNORMAL = $04; + +type + {$EXTERNALSYM ATM_CAUSE_IE} + ATM_CAUSE_IE = record + Location : Byte; + Cause : Byte; + DiagnosticsLength : Byte; + Diagnostics : Array[0..3] of Byte; + end; + +// values used for the QOSClassForward and QOSClassBackward +// field in struct ATM_QOS_CLASS_IE +const + {$EXTERNALSYM QOS_CLASS0} + QOS_CLASS0 = $00; + {$EXTERNALSYM QOS_CLASS1} + QOS_CLASS1 = $01; + {$EXTERNALSYM QOS_CLASS2} + QOS_CLASS2 = $02; + {$EXTERNALSYM QOS_CLASS3} + QOS_CLASS3 = $03; + {$EXTERNALSYM QOS_CLASS4} + QOS_CLASS4 = $04; + +type + {$EXTERNALSYM ATM_QOS_CLASS_IE} + ATM_QOS_CLASS_IE = record + QOSClassForward : Byte; + QOSClassBackward : Byte; + end; + +// values used for the TypeOfNetworkId field in struct ATM_TRANSIT_NETWORK_SELECTION_IE +const + {$EXTERNALSYM TNS_TYPE_NATIONAL} + TNS_TYPE_NATIONAL = $40; + +// values used for the NetworkIdPlan field in struct ATM_TRANSIT_NETWORK_SELECTION_IE + {$EXTERNALSYM TNS_PLAN_CARRIER_ID_CODE} + TNS_PLAN_CARRIER_ID_CODE = $01; + +type + {$EXTERNALSYM ATM_TRANSIT_NETWORK_SELECTION_IE} + ATM_TRANSIT_NETWORK_SELECTION_IE = record + TypeOfNetworkId : Byte; + NetworkIdPlan : Byte; + NetworkIdLength : Byte; + NetworkId : Array[0..0] of Byte; + end; + +// ATM specific Ioctl codes +const + {$EXTERNALSYM SIO_GET_NUMBER_OF_ATM_DEVICES} + SIO_GET_NUMBER_OF_ATM_DEVICES = $50160001; + {$EXTERNALSYM SIO_GET_ATM_ADDRESS} + SIO_GET_ATM_ADDRESS = $d0160002; + {$EXTERNALSYM SIO_ASSOCIATE_PVC} + SIO_ASSOCIATE_PVC = $90160003; + {$EXTERNALSYM SIO_GET_ATM_CONNECTION_ID} + SIO_GET_ATM_CONNECTION_ID = $50160004; + +// ATM Connection Identifier +type + {$EXTERNALSYM ATM_CONNECTION_ID} + ATM_CONNECTION_ID = record + DeviceNumber : DWORD; + VPI : DWORD; + VCI : DWORD; + end; + +// Input buffer format for SIO_ASSOCIATE_PVC + {$EXTERNALSYM ATM_PVC_PARAMS} + ATM_PVC_PARAMS = record + PvcConnectionId : ATM_CONNECTION_ID; + PvcQos : QOS; + end; + + {$NODEFINE InitializeWinSock} + procedure InitializeWinSock; + {$NODEFINE UninitializeWinSock} + procedure UninitializeWinSock; + procedure InitializeStubsEx; + function Winsock2Loaded: Boolean; + function WinsockHandle : THandle; + +//JPM +{ +I made these symbols up so to prevent range check warnings in FreePascal. +SizeOf is a smallInt when an expression is evaluated at run-time. This +run-time evaluation makes no sense because the compiler knows these when compiling +so it should give us the numbers. + +} +const + {$EXTERNALSYM SIZE_WSACMSGHDR} + SIZE_WSACMSGHDR = DWORD(SizeOf(WSACMSGHDR)); + {$EXTERNALSYM SIZE_FARPROC} + SIZE_FARPROC = DWORD(SizeOf(FARPROC)); + {$EXTERNALSYM MAX_NATURAL_ALIGNMENT_SUB_1} + MAX_NATURAL_ALIGNMENT_SUB_1 = DWORD(MAX_NATURAL_ALIGNMENT - 1); + {$EXTERNALSYM SIZE_IP_MSFILTER} + SIZE_IP_MSFILTER = DWORD(SizeOf(ip_msfilter)); + {$EXTERNALSYM SIZE_TINADDR} + SIZE_TINADDR = DWORD(SizeOf(TInAddr)); + {$EXTERNALSYM SIZE_TIN6ADDR} + SIZE_TIN6ADDR = DWORD(SizeOf(TIn6Addr)); + {$EXTERNALSYM SIZE_TSOCKADDRIN} + SIZE_TSOCKADDRIN = DWORD(SizeOf(TSockAddrIn)); + {$EXTERNALSYM SIZE_TSOCKADDRIN6} + SIZE_TSOCKADDRIN6 = DWORD(SizeOf(TSockAddrIn6)); + {$EXTERNALSYM SIZE_GROUP_FILTER} + SIZE_GROUP_FILTER = DWORD(SizeOf(GROUP_FILTER)); + {$EXTERNALSYM SIZE_TADDRINFO} + SIZE_TADDRINFO = DWORD(SizeOf(TAddrInfo)); + {$EXTERNALSYM SIZE_SOCKADDR_STORAGE} + SIZE_SOCKADDR_STORAGE = DWORD(sizeof(SOCKADDR_STORAGE)); + {$IFNDEF WINCE} + {$EXTERNALSYM SIZE_TWSAMSG} + SIZE_TWSAMSG = DWORD(SizeOf(TWSAMSG)); + {$ENDIF} + {$EXTERNALSYM SIZE_GUID} + SIZE_GUID = DWORD(SizeOf(TGuid)); + {$EXTERNALSYM SIZE_INTEGER} + SIZE_INTEGER = DWORD(SizeOf(Integer)); + +//============================================================= + +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Module Name: +// +// mstcpip.h +// +// Abstract: +// +// This module contains Microsoft-specific extensions to the core +// Winsock definitions. +// +// Environment: +// +// user mode or kernel mode + +type + {$EXTERNALSYM tcp_keepalive} + tcp_keepalive = record + onoff: u_long; + keepalivetime: u_long; + keepaliveinterval: u_long; + end; + +const + {$EXTERNALSYM SIO_KEEPALIVE_VALS} + SIO_KEEPALIVE_VALS = DWORD(IOC_IN or IOC_VENDOR or 4); + + RSWinsockCallError = 'Error on call to Winsock2 library function %s'; + RSWinsockLoadError = 'Error on loading Winsock2 library (%s)'; + +//============================================================= +implementation +//============================================================= + +var + hWinSockDll : THandle = 0; // WS2_32.DLL handle + {$IFNDEF WINCE} + hMSWSockDll : THandle = 0; // MSWSOCK.DLL handle + {$ENDIF} + +function WinsockHandle : THandle; +begin + Result := hWinSockDll; +end; + +function Winsock2Loaded : Boolean; +begin + Result := hWinSockDll <> 0; +end; + +procedure InitializeWinSock; +var + LData: TWSAData; + LError: DWORD; +begin + if hWinSockDll = 0 then begin + hWinSockDll := SafeLoadLibrary(WINSOCK2_DLL); + if hWinSockDll <> 0 then begin + LError := WSAStartup($202, LData); + if LError = 0 then begin + Exit; + end; + Winapi.Windows.FreeLibrary(hWinSockDll); + hWinSockDll := 0; + end else begin + LError := Winapi.Windows.GetLastError; + end; + raise EIdWinsockStubError.Build(LError, RSWinsockLoadError, [WINSOCK2_DLL]); + end; +end; + +{$IFNDEF WINCE} +procedure LoadMSWSock; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if hMSWSockDll = 0 then begin + hMSWSockDll := SafeLoadLibrary(MSWSOCK_DLL); + if hMSWSockDll = 0 then begin + raise EIdWinsockStubError.Build(Winapi.Windows.GetLastError, RSWinsockLoadError, [MSWSOCK_DLL]); + end; + end; +end; +{$ENDIF} + +procedure UninitializeWinSock; +begin +{$IFNDEF WINCE} + if hMSWSockDll <> 0 then + begin + FreeLibrary(hMSWSockDll); + hMSWSockDll := 0; + end; +{$ENDIF} + if hWinSockDll <> 0 then + begin + WSACleanup; + FreeLibrary(hWinSockDll); + hWinSockDll := 0; + end; +end; + +constructor EIdWinsockStubError.Build(AWin32Error: DWORD; const ATitle: String; AArgs: array of const); +begin + FTitle := Format(ATitle, AArgs, TFormatSettings.Create); + FWin32Error := AWin32Error; + if AWin32Error = 0 then begin + inherited Create(FTitle); + end else + begin + FWin32ErrorMessage := System.SysUtils.SysErrorMessage(AWin32Error); + inherited Create(FTitle + ': ' + FWin32ErrorMessage); {Do not Localize} + end; +end; + +{ IMPORTANT!!! + +WindowsCE only has a Unicode (WideChar) version of GetProcAddress. We could use +a version of GetProcAddress in the FreePascal dynlibs unit but that does a +conversion from ASCII to Unicode which might not be necessary since most calls +pass a constant anyway. +} +function FixupStub(hDll: THandle; const AName:{$IFDEF WINCE}TIdUnicodeString{$ELSE}string{$ENDIF}): Pointer; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if hDll = 0 then begin + raise EIdWinsockStubError.Build(WSANOTINITIALISED, RSWinsockCallError, [AName]); + end; + Result := Winapi.Windows.GetProcAddress(hDll, {$IFDEF WINCE}PWideChar{$ELSE}PChar{$ENDIF}(AName)); + if Result = nil then begin + raise EIdWinsockStubError.Build(WSAEINVAL, RSWinsockCallError, [AName]); + end; +end; + +function FixupStubEx(hSocket: TSocket; const AName: string; const AGuid: TGUID): Pointer; +var + LStatus: LongInt; + LBytesSend: DWORD; +begin + LStatus := WSAIoctl(hSocket, SIO_GET_EXTENSION_FUNCTION_POINTER, @AGuid, LongWord(SIZE_GUID), + @Result, SIZE_FARPROC, @LBytesSend, nil, nil); + if LStatus <> 0 then begin + raise EIdWinsockStubError.Build(WSAGetLastError, RSWinsockCallError, [AName]); + end; +end; + +function Stub_WSAStartup(const wVersionRequired: word; out WSData: TWSAData): Integer; stdcall; +begin + @WSAStartup := FixupStub(hWinSockDll, 'WSAStartup'); {Do not Localize} + Result := WSAStartup(wVersionRequired, WSData); +end; + +function Stub_WSACleanup: Integer; stdcall; +begin + @WSACleanup := FixupStub(hWinSockDll, 'WSACleanup'); {Do not Localize} + Result := WSACleanup; +end; + +function Stub_accept(const s: TSocket; AAddr: PSockAddr; addrlen: PInteger): TSocket; stdcall; +begin + @accept := FixupStub(hWinSockDll, 'accept'); {Do not Localize} + Result := accept(s, AAddr, addrlen); +end; + +function Stub_bind(const s: TSocket; const name: PSockAddr; const namelen: Integer): Integer; stdcall; +begin + @bind := FixupStub(hWinSockDll, 'bind'); {Do not Localize} + Result := bind(s, name, namelen); +end; + +function Stub_closesocket(const s: TSocket): Integer; stdcall; +begin + @closesocket := FixupStub(hWinSockDll, 'closesocket'); {Do not Localize} + Result := closesocket(s); +end; + +function Stub_connect(const s: TSocket; const name: PSockAddr; const namelen: Integer): Integer; stdcall; +begin + @connect := FixupStub(hWinSockDll, 'connect'); {Do not Localize} + Result := connect(s, name, namelen); +end; + +function Stub_ioctlsocket(const s: TSocket; const cmd: DWORD; var arg: u_long): Integer; stdcall; +begin + @ioctlsocket := FixupStub(hWinSockDll, 'ioctlsocket'); {Do not Localize} + Result := ioctlsocket(s, cmd, arg); +end; + +function Stub_getpeername(const s: TSocket; const name: PSockAddr; var namelen: Integer): Integer; stdcall; +begin + @getpeername := FixupStub(hWinSockDll, 'getpeername'); {Do not Localize} + Result := getpeername(s, name, namelen); +end; + +function Stub_getsockname(const s: TSocket; const name: PSockAddr; var namelen: Integer): Integer; stdcall; +begin + @getsockname := FixupStub(hWinSockDll, 'getsockname'); {Do not Localize} + Result := getsockname(s, name, namelen); +end; + +function Stub_getsockopt(const s: TSocket; const level, optname: Integer; optval: PAnsiChar; var optlen: Integer): Integer; stdcall; +begin + @getsockopt := FixupStub(hWinSockDll, 'getsockopt'); {Do not Localize} + Result := getsockopt(s, level, optname, optval, optlen); +end; + +function Stub_htonl(hostlong: u_long): u_long; stdcall; +begin + @htonl := FixupStub(hWinSockDll, 'htonl'); {Do not Localize} + Result := htonl(hostlong); +end; + +function Stub_htons(hostshort: u_short): u_short; stdcall; +begin + @htons := FixupStub(hWinSockDll, 'htons'); {Do not Localize} + Result := htons(hostshort); +end; + +function Stub_inet_addr(cp: PAnsiChar): u_long; stdcall; +begin + @inet_addr := FixupStub(hWinSockDll, 'inet_addr'); {Do not Localize} + Result := inet_addr(cp); +end; + +function Stub_inet_ntoa(inaddr: TInAddr): PAnsiChar; stdcall; +begin + @inet_ntoa := FixupStub(hWinSockDll, 'inet_ntoa'); {Do not Localize} + Result := inet_ntoa(inaddr); +end; + +function Stub_listen(const s: TSocket; backlog: Integer): Integer; stdcall; +begin + @listen := FixupStub(hWinSockDll, 'listen'); {Do not Localize} + Result := listen(s, backlog); +end; + +function Stub_ntohl(netlong: u_long): u_long; stdcall; +begin + @ntohl := FixupStub(hWinSockDll, 'ntohl'); {Do not Localize} + Result := ntohl(netlong); +end; + +function Stub_ntohs(netshort: u_short): u_short; stdcall; +begin + @ntohs := FixupStub(hWinSockDll, 'ntohs'); {Do not Localize} + Result := ntohs(netshort); +end; + +function Stub_recv(const s: TSocket; var Buf; len, flags: Integer): Integer; stdcall; +begin + @recv := FixupStub(hWinSockDll, 'recv'); {Do not Localize} + Result := recv(s, Buf, len, flags); +end; + +function Stub_recvfrom(const s: TSocket; var Buf; len, flags: Integer; from: PSockAddr; fromlen: PInteger): Integer; stdcall; +begin + @recvfrom := FixupStub(hWinSockDll, 'recvfrom'); {Do not Localize} + Result := recvfrom(s, Buf, len, flags, from, fromlen); +end; + +function Stub_select(nfds: Integer; readfds, writefds, exceptfds: PFDSet; timeout: PTimeVal): Integer; stdcall; +begin + @select := FixupStub(hWinSockDll, 'select'); {Do not Localize} + Result := select(nfds, readfds, writefds, exceptfds, timeout); +end; + +function Stub_send(const s: TSocket; const Buf; len, flags: Integer): Integer; stdcall; +begin + @send := FixupStub(hWinSockDll, 'send'); {Do not Localize} + Result := send(s, Buf, len, flags); +end; + +function Stub_sendto(const s: TSocket; const Buf; const len, flags: Integer; const addrto: PSockAddr; const tolen: Integer): Integer; stdcall; +begin + @sendto := FixupStub(hWinSockDll, 'sendto'); {Do not Localize} + Result := sendto(s, Buf, len, flags, addrto, tolen); +end; + +function Stub_setsockopt(const s: TSocket; const level, optname: Integer; optval: PAnsiChar; const optlen: Integer): Integer; stdcall; +begin + @setsockopt := FixupStub(hWinSockDll, 'setsockopt'); {Do not Localize} + Result := setsockopt(s, level, optname, optval, optlen); +end; + +function Stub_shutdown(const s: TSocket; const how: Integer): Integer; stdcall; +begin + @shutdown := FixupStub(hWinSockDll, 'shutdown'); {Do not Localize} + Result := shutdown(s, how); +end; + +function Stub_socket(const af, istruct, protocol: Integer): TSocket; stdcall; +begin + @socket := FixupStub(hWinSockDll, 'socket'); {Do not Localize} + Result := socket(af, istruct, protocol); +end; + +function Stub_gethostbyaddr(AAddr: Pointer; const len, addrtype: Integer): PHostEnt; stdcall; +begin + @gethostbyaddr := FixupStub(hWinSockDll, 'gethostbyaddr'); {Do not Localize} + Result := gethostbyaddr(AAddr, len, addrtype); +end; + +function Stub_gethostbyname(name: PAnsiChar): PHostEnt; stdcall; +begin + @gethostbyname := FixupStub(hWinSockDll, 'gethostbyname'); {Do not Localize} + Result := gethostbyname(name); +end; + +{$IFDEF WINCE} +function Stub_sethostname(pName : PAnsiChar; cName : Integer) : Integer; stdcall; +begin + @sethostname := FixupStub(hWinSockDll, 'sethostname'); {Do not Localize} + Result := sethostname(pName, cName); +end; +{$ENDIF} + +function Stub_gethostname(name: PAnsiChar; len: Integer): Integer; stdcall; +begin + @gethostname := FixupStub(hWinSockDll, 'gethostname'); {Do not Localize} + Result := gethostname(name, len); +end; + +function Stub_getservbyport(const port: Integer; const proto: PAnsiChar): PServEnt; stdcall; +begin + @getservbyport := FixupStub(hWinSockDll, 'getservbyport'); {Do not Localize} + Result := getservbyport(port, proto); +end; + +function Stub_getservbyname(const name, proto: PAnsiChar): PServEnt; stdcall; +begin + @getservbyname := FixupStub(hWinSockDll, 'getservbyname'); {Do not Localize} + Result := getservbyname(name, proto); +end; + +function Stub_getprotobynumber(const proto: Integer): PProtoEnt; stdcall; +begin + @getprotobynumber := FixupStub(hWinSockDll, 'getprotobynumber'); {Do not Localize} + Result := getprotobynumber(proto); +end; + +function Stub_getprotobyname(const name: PAnsiChar): PProtoEnt; stdcall; +begin + @getprotobyname := FixupStub(hWinSockDll, 'getprotobyname'); {Do not Localize} + Result := getprotobyname(name); +end; + +procedure Stub_WSASetLastError(const iError: Integer); stdcall; +begin + @WSASetLastError := FixupStub(hWinSockDll, 'WSASetLastError'); {Do not Localize} + WSASetLastError(iError); +end; + +function Stub_WSAGetLastError: Integer; stdcall; +begin + @WSAGetLastError := FixupStub(hWinSockDll, 'WSAGetLastError'); {Do not Localize} + Result := WSAGetLastError; +end; + +{$IFNDEF WINCE} +function Stub_WSAIsBlocking: BOOL; stdcall; +begin + @WSAIsBlocking := FixupStub(hWinSockDll, 'WSAIsBlocking'); {Do not Localize} + Result := WSAIsBlocking; +end; + +function Stub_WSAUnhookBlockingHook: Integer; stdcall; +begin + @WSAUnhookBlockingHook := FixupStub(hWinSockDll, 'WSAUnhookBlockingHook'); {Do not Localize} + Result := WSAUnhookBlockingHook; +end; + +function Stub_WSASetBlockingHook(lpBlockFunc: TFarProc): TFarProc; stdcall; +begin + @WSASetBlockingHook := FixupStub(hWinSockDll, 'WSASetBlockingHook'); {Do not Localize} + Result := WSASetBlockingHook(lpBlockFunc); +end; + +function Stub_WSACancelBlockingCall: Integer; stdcall; +begin + @WSACancelBlockingCall := FixupStub(hWinSockDll, 'WSACancelBlockingCall'); {Do not Localize} + Result := WSACancelBlockingCall; +end; + +function Stub_WSAAsyncGetServByName(HWindow: HWND; wMsg: u_int; name, proto, buf: PAnsiChar; buflen: Integer): THandle; stdcall; +begin + @WSAAsyncGetServByName := FixupStub(hWinSockDll, 'WSAAsyncGetServByName'); {Do not Localize} + Result := WSAAsyncGetServByName(HWindow, wMsg, name, proto, buf, buflen); +end; + +function Stub_WSAAsyncGetServByPort(HWindow: HWND; wMsg, port: u_int; proto, buf: PAnsiChar; buflen: Integer): THandle; stdcall; +begin + @WSAAsyncGetServByPort := FixupStub(hWinSockDll, 'WSAAsyncGetServByPort'); {Do not Localize} + Result := WSAAsyncGetServByPort(HWindow, wMsg, port, proto, buf, buflen); +end; + +function Stub_WSAAsyncGetProtoByName(HWindow: HWND; wMsg: u_int; name, buf: PAnsiChar; buflen: Integer): THandle; stdcall; +begin + @WSAAsyncGetProtoByName := FixupStub(hWinSockDll, 'WSAAsyncGetProtoByName'); {Do not Localize} + Result := WSAAsyncGetProtoByName(HWindow, wMsg, name, buf, buflen); +end; + +function Stub_WSAAsyncGetProtoByNumber(HWindow: HWND; wMsg: u_int; number: Integer; buf: PAnsiChar; buflen: Integer): THandle; stdcall; +begin + @WSAAsyncGetProtoByNumber := FixupStub(hWinSockDll, 'WSAAsyncGetProtoByNumber'); {Do not Localize} + Result := WSAAsyncGetProtoByNumber(HWindow, wMsg, number, buf, buflen); +end; + +function Stub_WSAAsyncGetHostByName(HWindow: HWND; wMsg: u_int; name, buf: PAnsiChar; buflen: Integer): THandle; stdcall; +begin + @WSAAsyncGetHostByName := FixupStub(hWinSockDll, 'WSAAsyncGetHostByName'); {Do not Localize} + Result := WSAAsyncGetHostByName(HWindow, wMsg, name, buf, buflen); +end; + +function Stub_WSAAsyncGetHostByAddr(HWindow: HWND; wMsg: u_int; AAddr: PAnsiChar; len, istruct: Integer; buf: PAnsiChar; buflen: Integer): THandle; stdcall; +begin + @WSAAsyncGetHostByAddr := FixupStub(hWinSockDll, 'WSAAsyncGetHostByAddr'); {Do not Localize} + Result := WSAAsyncGetHostByAddr(HWindow, wMsg, AAddr, len, istruct, buf, buflen); +end; + +function Stub_WSACancelAsyncRequest(hAsyncTaskHandle: THandle): Integer; stdcall; +begin + @WSACancelAsyncRequest := FixupStub(hWinSockDll, 'WSACancelAsyncRequest'); {Do not Localize} + Result := WSACancelAsyncRequest(hAsyncTaskHandle); +end; + +function Stub_WSAAsyncSelect(const s: TSocket; HWindow: HWND; wMsg: u_int; lEvent: Longint): Integer; stdcall; +begin + @WSAAsyncSelect := FixupStub(hWinSockDll, 'WSAAsyncSelect'); {Do not Localize} + Result := WSAAsyncSelect(s, HWindow, wMsg, lEvent); +end; +{$ENDIF} + +function Stub___WSAFDIsSet(const s: TSocket; var FDSet: TFDSet): Bool; stdcall; +begin + @__WSAFDIsSet := FixupStub(hWinSockDll, '__WSAFDIsSet'); {Do not Localize} + Result := __WSAFDIsSet(s, FDSet); +end; + +function Stub_WSAAccept(const s: TSocket; AAddr: PSockAddr; addrlen: PInteger; lpfnCondition: LPCONDITIONPROC; const dwCallbackData: DWORD): TSocket; stdcall; +begin + @WSAAccept := FixupStub(hWinSockDll, 'WSAAccept'); {Do not Localize} + Result := WSAAccept(s, AAddr, addrlen, lpfnCondition, dwCallbackData); +end; + +function Stub_WSACloseEvent(const hEvent: wsaevent): WordBool; stdcall; +begin + @WSACloseEvent := FixupStub(hWinSockDll, 'WSACloseEvent'); {Do not Localize} + Result := WSACloseEvent(hEvent); +end; + +function Stub_WSAConnect(const s: TSocket; const name: PSockAddr; const namelen: Integer; lpCallerData, lpCalleeData: LPWSABUF; lpSQOS, lpGQOS: LPQOS): Integer; stdcall; +begin + @WSAConnect := FixupStub(hWinSockDll, 'WSAConnect'); {Do not Localize} + Result := WSAConnect(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS); +end; + +function Stub_WSACreateEvent: wsaevent; stdcall; +begin + @WSACreateEvent := FixupStub(hWinSockDll, 'WSACreateEvent'); {Do not Localize} + Result := WSACreateEvent; +end; + +{$IFNDEF WINCE} +function Stub_WSADuplicateSocketA(const s: TSocket; const dwProcessId: DWORD; lpProtocolInfo: LPWSAPROTOCOL_INFOA): Integer; stdcall; +begin + @WSADuplicateSocketA := FixupStub(hWinSockDll, 'WSADuplicateSocketA'); {Do not Localize} + Result := WSADuplicateSocketA(s, dwProcessId, lpProtocolInfo); +end; + +function Stub_WSADuplicateSocketW(const s: TSocket; const dwProcessId: DWORD; lpProtocolInfo: LPWSAPROTOCOL_INFOW): Integer; stdcall; +begin + @WSADuplicateSocketW := FixupStub(hWinSockDll, 'WSADuplicateSocketW'); {Do not Localize} + Result := WSADuplicateSocketW(s, dwProcessId, lpProtocolInfo); +end; + +function Stub_WSADuplicateSocket(const s: TSocket; const dwProcessId: DWORD; lpProtocolInfo: LPWSAPROTOCOL_INFO): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSADuplicateSocket := FixupStub(hWinSockDll, 'WSADuplicateSocketW'); {Do not Localize} + {$ELSE} + @WSADuplicateSocket := FixupStub(hWinSockDll, 'WSADuplicateSocketA'); {Do not Localize} + {$ENDIF} + Result := WSADuplicateSocket(s, dwProcessId, lpProtocolInfo); +end; +{$ENDIF} + +function Stub_WSAEnumNetworkEvents(const s: TSocket; const hEventObject: WSAEVENT; lpNetworkEvents: LPWSANETWORKEVENTS): Integer; stdcall; +begin + @WSAEnumNetworkEvents := FixupStub(hWinSockDll, 'WSAEnumNetworkEvents'); {Do not Localize} + Result := WSAEnumNetworkEvents(s, hEventObject, lpNetworkEvents); +end; + +function Stub_WSAEnumProtocolsA(lpiProtocols: PInteger; lpProtocolBuffer: LPWSAPROTOCOL_INFOA; var lpdwBufferLength: DWORD): Integer; stdcall; +begin + @WSAEnumProtocolsA := FixupStub(hWinSockDll, 'WSAEnumProtocolsA'); {Do not Localize} + Result := WSAEnumProtocolsA(lpiProtocols, lpProtocolBuffer, lpdwBufferLength); +end; + +function Stub_WSAEnumProtocolsW(lpiProtocols: PInteger; lpProtocolBuffer: LPWSAPROTOCOL_INFOW; var lpdwBufferLength: DWORD): Integer; stdcall; +begin + @WSAEnumProtocolsW := FixupStub(hWinSockDll, 'WSAEnumProtocolsW'); {Do not Localize} + Result := WSAEnumProtocolsW(lpiProtocols, lpProtocolBuffer, lpdwBufferLength); +end; + +function Stub_WSAEnumProtocols(lpiProtocols: PInteger; lpProtocolBuffer: LPWSAPROTOCOL_INFO; var lpdwBufferLength: DWORD): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAEnumProtocols := FixupStub(hWinSockDll, 'WSAEnumProtocolsW'); {Do not Localize} + {$ELSE} + @WSAEnumProtocols := FixupStub(hWinSockDll, 'WSAEnumProtocolsA'); {Do not Localize} + {$ENDIF} + Result := WSAEnumProtocols(lpiProtocols, lpProtocolBuffer, lpdwBufferLength); +end; + +function Stub_WSAEventSelect(const s: TSocket; const hEventObject: WSAEVENT; lNetworkEvents: LongInt): Integer; stdcall; +begin + @WSAEventSelect := FixupStub(hWinSockDll, 'WSAEventSelect'); {Do not Localize} + Result := WSAEventSelect(s, hEventObject, lNetworkEvents); +end; + +function Stub_WSAGetOverlappedResult(const s: TSocket; AOverlapped: Pointer; lpcbTransfer: LPDWORD; fWait: BOOL; var lpdwFlags: DWORD): WordBool; stdcall; +begin + @WSAGetOverlappedResult := FixupStub(hWinSockDll, 'WSAGetOverlappedResult'); {Do not Localize} + Result := WSAGetOverlappedResult(s, AOverlapped, lpcbTransfer, fWait, lpdwFlags); +end; + +{$IFNDEF WINCE} +function Stub_WSAGetQOSByName(const s: TSocket; lpQOSName: LPWSABUF; lpQOS: LPQOS): WordBool; stdcall; +begin + @WSAGetQOSByName := FixupStub(hWinSockDll, 'WSAGetQOSByName'); {Do not Localize} + Result := WSAGetQOSByName(s, lpQOSName, lpQOS); +end; +{$ENDIF} + +function Stub_WSAHtonl(const s: TSocket; hostlong: u_long; var lpnetlong: DWORD): Integer; stdcall; +begin + @WSAHtonl := FixupStub(hWinSockDll, 'WSAHtonl'); {Do not Localize} + Result := WSAHtonl(s, hostlong, lpnetlong); +end; + +function Stub_WSAHtons(const s: TSocket; hostshort: u_short; var lpnetshort: WORD): Integer; stdcall; +begin + @WSAHtons := FixupStub(hWinSockDll, 'WSAHtons'); {Do not Localize} + Result := WSAHtons(s, hostshort, lpnetshort); +end; + +function Stub_WSAIoctl(const s: TSocket; dwIoControlCode: DWORD; lpvInBuffer: Pointer; cbInBuffer: DWORD; lpvOutBuffer: Pointer; cbOutBuffer: DWORD; lpcbBytesReturned: LPDWORD; AOverlapped: Pointer; lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSAIoctl := FixupStub(hWinSockDll, 'WSAIoctl'); {Do not Localize} + Result := WSAIoctl(s, dwIoControlCode, lpvInBuffer, cbInBuffer, lpvOutBuffer, cbOutBuffer, lpcbBytesReturned, AOverlapped, lpCompletionRoutine); +end; + +function Stub_WSAJoinLeaf(const s: TSocket; name: PSockAddr; namelen: Integer; lpCallerData, lpCalleeData: LPWSABUF; lpSQOS, lpGQOS: LPQOS; dwFlags: DWORD): TSocket; stdcall; +begin + @WSAJoinLeaf := FixupStub(hWinSockDll, 'WSAJoinLeaf'); {Do not Localize} + Result := WSAJoinLeaf(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS, dwFlags); +end; + +function Stub_WSANtohl(const s: TSocket; netlong: u_long; var lphostlong: DWORD): Integer; stdcall; +begin + @WSANtohl := FixupStub(hWinSockDll, 'WSANtohl'); {Do not Localize} + Result := WSANtohl(s, netlong, lphostlong); +end; + +function Stub_WSANtohs(const s: TSocket; netshort: u_short; var lphostshort: WORD): Integer; stdcall; +begin + @WSANtohs := FixupStub(hWinSockDll, 'WSANtohs'); {Do not Localize} + Result := WSANtohs(s, netshort, lphostshort); +end; + +function Stub_WSARecv(const s: TSocket; lpBuffers: LPWSABUF; dwBufferCount: DWORD; var lpNumberOfBytesRecvd: DWORD; var lpFlags: DWORD; AOverlapped: LPWSAOVERLAPPED; lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSARecv := FixupStub(hWinSockDll, 'WSARecv'); {Do not Localize} + Result := WSARecv(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, AOverlapped, lpCompletionRoutine); +end; + +function Stub_WSARecvDisconnect(const s: TSocket; lpInboundDisconnectData: LPWSABUF): Integer; stdcall; +begin + @WSARecvDisconnect := FixupStub(hWinSockDll, 'WSARecvDisconnect'); {Do not Localize} + Result := WSARecvDisconnect(s, lpInboundDisconnectData); +end; + +function Stub_WSARecvFrom(const s: TSocket; lpBuffers: LPWSABUF; dwBufferCount: DWORD; var lpNumberOfBytesRecvd: DWORD; var lpFlags: DWORD; lpFrom: PSockAddr; lpFromlen: PInteger; AOverlapped: Pointer; lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSARecvFrom := FixupStub(hWinSockDll, 'WSARecvFrom'); {Do not Localize} + Result := WSARecvFrom(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpFrom, lpFromlen, AOverlapped, lpCompletionRoutine); +end; + +function Stub_WSAResetEvent(hEvent: wsaevent): WordBool; stdcall; +begin + @WSAResetEvent := FixupStub(hWinSockDll, 'WSAResetEvent'); {Do not Localize} + Result := WSAResetEvent(hEvent); +end; + +function Stub_WSASend(const s: TSocket; lpBuffers: LPWSABUF; dwBufferCount: DWORD; var lpNumberOfBytesSent: DWORD; dwFlags: DWORD; AOverlapped: LPWSAOVERLAPPED; lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSASend := FixupStub(hWinSockDll, 'WSASend'); {Do not Localize} + Result := WSASend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, AOverlapped, lpCompletionRoutine); +end; + +{$IFNDEF WINCE} +function Stub_WSASendDisconnect(const s: TSocket; lpOutboundDisconnectData: LPWSABUF): Integer; stdcall; +begin + @WSASendDisconnect := FixupStub(hWinSockDll, 'WSASendDisconnect'); {Do not Localize} + Result := WSASendDisconnect(s, lpOutboundDisconnectData); +end; +{$ENDIF} + +function Stub_WSASendTo(const s: TSocket; lpBuffers: LPWSABUF; dwBufferCount: DWORD; var lpNumberOfBytesSent: DWORD; dwFlags: DWORD; lpTo: PSOCKADDR; iTolen: Integer; AOverlapped: LPWSAOVERLAPPED; lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSASendTo := FixupStub(hWinSockDll, 'WSASendTo'); {Do not Localize} + Result := WSASendTo(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo, iTolen, AOverlapped, lpCompletionRoutine); +end; + +function Stub_WSASetEvent(hEvent: WSAEVENT): WordBool; stdcall; +begin + @WSASetEvent := FixupStub(hWinSockDll, 'WSASetEvent'); {Do not Localize} + Result := WSASetEvent(hEvent); +end; + +function Stub_WSASocketA(af, iType, protocol: Integer; lpProtocolInfo: LPWSAPROTOCOL_INFOA; g: GROUP; dwFlags: DWORD): TSocket; stdcall; +begin + @WSASocketA := FixupStub(hWinSockDll, 'WSASocketA'); {Do not Localize} + Result := WSASocketA(af, iType, protocol, lpProtocolInfo, g, dwFlags); +end; + +function Stub_WSASocketW(af, iType, protocol: Integer; lpProtocolInfo: LPWSAPROTOCOL_INFOW; g: GROUP; dwFlags: DWORD): TSocket; stdcall; +begin + @WSASocketW := FixupStub(hWinSockDll, 'WSASocketW'); {Do not Localize} + Result := WSASocketW(af, iType, protocol, lpProtocolInfo, g, dwFlags); +end; + +function Stub_WSASocket(af, iType, protocol: Integer; lpProtocolInfo: LPWSAPROTOCOL_INFO; g: GROUP; dwFlags: DWORD): TSocket; stdcall; +begin + {$IFDEF UNICODE} + @WSASocket := FixupStub(hWinSockDll, 'WSASocketW'); {Do not Localize} + {$ELSE} + @WSASocket := FixupStub(hWinSockDll, 'WSASocketA'); {Do not Localize} + {$ENDIF} + Result := WSASocket(af, iType, protocol, lpProtocolInfo, g, dwFlags); +end; + +function Stub_WSAWaitForMultipleEvents(cEvents: DWORD; lphEvents: Pwsaevent; fWaitAll: LongBool; dwTimeout: DWORD; fAlertable: LongBool): DWORD; stdcall; +begin + @WSAWaitForMultipleEvents := FixupStub(hWinSockDll, 'WSAWaitForMultipleEvents'); {Do not Localize} + Result := WSAWaitForMultipleEvents(cEvents, lphEvents, fWaitAll, dwTimeout, fAlertable); +end; + +function Stub_WSAAddressToStringA(lpsaAddress: PSockAddr; const dwAddressLength: DWORD; const lpProtocolInfo: LPWSAPROTOCOL_INFOA; const lpszAddressString: PAnsiChar; var lpdwAddressStringLength: DWORD): Integer; stdcall; +begin + @WSAAddressToStringA := FixupStub(hWinSockDll, 'WSAAddressToStringA'); {Do not Localize} + Result := WSAAddressToStringA(lpsaAddress, dwAddressLength, lpProtocolInfo, lpszAddressString, lpdwAddressStringLength); +end; + +function Stub_WSAAddressToStringW(lpsaAddress: PSockAddr; const dwAddressLength: DWORD; const lpProtocolInfo: LPWSAPROTOCOL_INFOW; const lpszAddressString: PWideChar; var lpdwAddressStringLength: DWORD): Integer; stdcall; +begin + @WSAAddressToStringW := FixupStub(hWinSockDll, 'WSAAddressToStringW'); {Do not Localize} + Result := WSAAddressToStringW(lpsaAddress, dwAddressLength, lpProtocolInfo, lpszAddressString, lpdwAddressStringLength); +end; + +function Stub_WSAAddressToString(lpsaAddress: PSockAddr; const dwAddressLength: DWORD; const lpProtocolInfo: LPWSAPROTOCOL_INFO; + const lpszAddressString: {$IFDEF UNICODE}PWideChar{$ELSE}PAnsiChar{$ENDIF}; var lpdwAddressStringLength: DWORD): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAAddressToString := FixupStub(hWinSockDll, 'WSAAddressToStringW'); {Do not Localize} + {$ELSE} + @WSAAddressToString := FixupStub(hWinSockDll, 'WSAAddressToStringA'); {Do not Localize} + {$ENDIF} + Result := WSAAddressToString(lpsaAddress, dwAddressLength, lpProtocolInfo, lpszAddressString, lpdwAddressStringLength); +end; + +function Stub_WSAStringToAddressA(const AddressString: PAnsiChar; const AddressFamily: Integer; const lpProtocolInfo: LPWSAPROTOCOL_INFOA; var lpAddress: TSockAddr; var lpAddressLength: Integer): Integer; stdcall; +begin + @WSAStringToAddressA := FixupStub(hWinSockDll, 'WSAStringToAddressA'); {Do not Localize} + Result := WSAStringToAddressA(AddressString, AddressFamily, lpProtocolInfo, lpAddress, lpAddressLength); +end; + +function Stub_WSAStringToAddressW(const AddressString: PWideChar; const AddressFamily: Integer; const lpProtocolInfo: LPWSAPROTOCOL_INFOW; var lpAddress: TSockAddr; var lpAddressLength: Integer): Integer; stdcall; +begin + @WSAStringToAddressW := FixupStub(hWinSockDll, 'WSAStringToAddressW'); {Do not Localize} + Result := WSAStringToAddressW(AddressString, AddressFamily, lpProtocolInfo, lpAddress, lpAddressLength); +end; + +function Stub_WSAStringToAddress (const AddressString: {$IFDEF UNICODE}PWideChar{$ELSE}PAnsiChar{$ENDIF}; + const AddressFamily: Integer; const lpProtocolInfo: LPWSAProtocol_Info; + var lpAddress: TSockAddr; var lpAddressLength: Integer): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAStringToAddress := FixupStub(hWinSockDll, 'WSAStringToAddressW'); {Do not Localize} + {$ELSE} + @WSAStringToAddress := FixupStub(hWinSockDll, 'WSAStringToAddressA'); {Do not Localize} + {$ENDIF} + Result := WSAStringToAddress(AddressString, AddressFamily, lpProtocolInfo, lpAddress, lpAddressLength); +end; + +function Stub_WSALookupServiceBeginA(var qsRestrictions: TWSAQuerySetA; const dwControlFlags: DWORD; var hLookup: THandle): Integer; stdcall; +begin + @WSALookupServiceBeginA := FixupStub(hWinSockDll, 'WSALookupServiceBeginA'); {Do not Localize} + Result := WSALookupServiceBeginA(qsRestrictions, dwControlFlags, hLookup); +end; + +function Stub_WSALookupServiceBeginW(var qsRestrictions: TWSAQuerySetW; const dwControlFlags: DWORD; var hLookup: THandle): Integer; stdcall; +begin + @WSALookupServiceBeginW := FixupStub(hWinSockDll, 'WSALookupServiceBeginW'); {Do not Localize} + Result := WSALookupServiceBeginW(qsRestrictions, dwControlFlags, hLookup); +end; + +function Stub_WSALookupServiceBegin(var qsRestrictions: TWSAQuerySet; const dwControlFlags: DWORD; var hLookup: THandle): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSALookupServiceBegin := FixupStub(hWinSockDll, 'WSALookupServiceBeginW'); {Do not Localize} + {$ELSE} + @WSALookupServiceBegin := FixupStub(hWinSockDll, 'WSALookupServiceBeginA'); {Do not Localize} + {$ENDIF} + Result := WSALookupServiceBegin(qsRestrictions, dwControlFlags, hLookup); +end; + +function Stub_WSALookupServiceNextA(const hLookup: THandle; const dwControlFlags: DWORD; var dwBufferLength: DWORD; lpqsResults: LPWSAQUERYSETA): Integer; stdcall; +begin + @WSALookupServiceNextA := FixupStub(hWinSockDll, 'WSALookupServiceNextA'); {Do not Localize} + Result := WSALookupServiceNextA(hLookup, dwControlFlags, dwBufferLength, lpqsResults); +end; + +function Stub_WSALookupServiceNextW(const hLookup: THandle; const dwControlFlags: DWORD; var dwBufferLength: DWORD; lpqsResults: LPWSAQUERYSETW): Integer; stdcall; +begin + @WSALookupServiceNextW := FixupStub(hWinSockDll, 'WSALookupServiceNextW'); {Do not Localize} + Result := WSALookupServiceNextW(hLookup, dwControlFlags, dwBufferLength, lpqsResults); +end; + +function Stub_WSALookupServiceNext(const hLookup: THandle; const dwControlFlags: DWORD; var dwBufferLength: DWORD; lpqsResults: LPWSAQUERYSET): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSALookupServiceNext := FixupStub(hWinSockDll, 'WSALookupServiceNextW'); {Do not Localize} + {$ELSE} + @WSALookupServiceNext := FixupStub(hWinSockDll, 'WSALookupServiceNextA'); {Do not Localize} + {$ENDIF} + Result := WSALookupServiceNext(hLookup, dwControlFlags, dwBufferLength, lpqsResults); +end; + +function Stub_WSALookupServiceEnd(const hLookup: THandle): Integer; stdcall; +begin + @WSALookupServiceEnd := FixupStub(hWinSockDll, 'WSALookupServiceEnd'); {Do not Localize} + Result := WSALookupServiceEnd(hLookup); +end; + + +function Stub_WSANSPIoctl(const hLookup : THANDLE; const dwControlCode : DWORD; + lpvInBuffer : Pointer; var cbInBuffer : DWORD; lpvOutBuffer : Pointer; + var cbOutBuffer : DWORD; var lpcbBytesReturned : DWORD; + lpCompletion : LPWSACOMPLETION) : Integer; stdcall; +begin + @WSANSPIoctl := FixupStub(hWinSockDLL, 'WSANSPIoctl'); {Do not Localize} + Result := WSANSPIoctl(hLookup,dwControlCode,lpvInBuffer,cbInBuffer,lpvOutBuffer, + cbOutBuffer, lpcbBytesReturned,lpCompletion); +end; + +function Stub_WSAInstallServiceClassA(const lpServiceClassInfo: LPWSASERVICECLASSINFOA): Integer; stdcall; +begin + @WSAInstallServiceClassA := FixupStub(hWinSockDll, 'WSAInstallServiceClassA'); {Do not Localize} + Result := WSAInstallServiceClassA(lpServiceClassInfo); +end; + +function Stub_WSAInstallServiceClassW(const lpServiceClassInfo: LPWSASERVICECLASSINFOW): Integer; stdcall; +begin + @WSAInstallServiceClassW := FixupStub(hWinSockDll, 'WSAInstallServiceClassW'); {Do not Localize} + Result := WSAInstallServiceClassW(lpServiceClassInfo); +end; + +function Stub_WSAInstallServiceClass(const lpServiceClassInfo: LPWSASERVICECLASSINFO): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAInstallServiceClass := FixupStub(hWinSockDll, 'WSAInstallServiceClassW'); {Do not Localize} + {$ELSE} + @WSAInstallServiceClass := FixupStub(hWinSockDll, 'WSAInstallServiceClassA'); {Do not Localize} + {$ENDIF} + Result := WSAInstallServiceClass(lpServiceClassInfo); +end; + +function Stub_WSARemoveServiceClass(const lpServiceClassId: PGUID): Integer; stdcall; +begin + @WSARemoveServiceClass := FixupStub(hWinSockDll, 'WSARemoveServiceClass'); {Do not Localize} + Result := WSARemoveServiceClass(lpServiceClassId); +end; + +function Stub_WSAGetServiceClassInfoA(const lpProviderId: PGUID; const lpServiceClassId: PGUID; var lpdwBufSize: DWORD; lpServiceClassInfo: LPWSASERVICECLASSINFOA): Integer; stdcall; +begin + @WSAGetServiceClassInfoA := FixupStub(hWinSockDll, 'WSAGetServiceClassInfoA'); {Do not Localize} + Result := WSAGetServiceClassInfoA(lpProviderId, lpServiceClassId, lpdwBufSize, lpServiceClassInfo); +end; + +function Stub_WSAGetServiceClassInfoW(const lpProviderId: PGUID; const lpServiceClassId: PGUID; var lpdwBufSize: DWORD; lpServiceClassInfo: LPWSASERVICECLASSINFOW): Integer; stdcall; +begin + @WSAGetServiceClassInfoW := FixupStub(hWinSockDll, 'WSAGetServiceClassInfoW'); {Do not Localize} + Result := WSAGetServiceClassInfoW(lpProviderId, lpServiceClassId, lpdwBufSize, lpServiceClassInfo); +end; + +function Stub_WSAGetServiceClassInfo(const lpProviderId: PGUID; const lpServiceClassId: PGUID; var lpdwBufSize: DWORD; lpServiceClassInfo: LPWSASERVICECLASSINFO): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAGetServiceClassInfo := FixupStub(hWinSockDll, 'WSAGetServiceClassInfoW'); {Do not Localize} + {$ELSE} + @WSAGetServiceClassInfo := FixupStub(hWinSockDll, 'WSAGetServiceClassInfoA'); {Do not Localize} + {$ENDIF} + Result := WSAGetServiceClassInfo(lpProviderId, lpServiceClassId, lpdwBufSize, lpServiceClassInfo); +end; + +function Stub_WSAEnumNameSpaceProvidersA(var lpdwBufferLength: DWORD; const lpnspBuffer: LPWSANAMESPACE_INFOA): Integer; stdcall; +begin + @WSAEnumNameSpaceProvidersA := FixupStub(hWinSockDll, 'WSAEnumNameSpaceProvidersA'); {Do not Localize} + Result := WSAEnumNameSpaceProvidersA(lpdwBufferLength, lpnspBuffer); +end; + +function Stub_WSAEnumNameSpaceProvidersW(var lpdwBufferLength: DWORD; const lpnspBuffer: LPWSANAMESPACE_INFOW): Integer; stdcall; +begin + @WSAEnumNameSpaceProvidersW := FixupStub(hWinSockDll, 'WSAEnumNameSpaceProvidersW'); {Do not Localize} + Result := WSAEnumNameSpaceProvidersW(lpdwBufferLength, lpnspBuffer); +end; + +function Stub_WSAEnumNameSpaceProviders(var lpdwBufferLength: DWORD; const lpnspBuffer: LPWSANAMESPACE_INFO): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAEnumNameSpaceProviders := FixupStub(hWinSockDll, 'WSAEnumNameSpaceProvidersW'); {Do not Localize} + {$ELSE} + @WSAEnumNameSpaceProviders := FixupStub(hWinSockDll, 'WSAEnumNameSpaceProvidersA'); {Do not Localize} + {$ENDIF} + Result := WSAEnumNameSpaceProviders(lpdwBufferLength, lpnspBuffer); +end; + +function Stub_WSAGetServiceClassNameByClassIdA(const lpServiceClassId: PGUID; lpszServiceClassName: PAnsiChar; var lpdwBufferLength: DWORD): Integer; stdcall; +begin + @WSAGetServiceClassNameByClassIdA := FixupStub(hWinSockDll, 'WSAGetServiceClassNameByClassIdA'); {Do not Localize} + Result := WSAGetServiceClassNameByClassIdA(lpServiceClassId, lpszServiceClassName, lpdwBufferLength); +end; + +function Stub_WSAGetServiceClassNameByClassIdW(const lpServiceClassId: PGUID; lpszServiceClassName: PWideChar; var lpdwBufferLength: DWORD): Integer; stdcall; +begin + @WSAGetServiceClassNameByClassIdW := FixupStub(hWinSockDll, 'WSAGetServiceClassNameByClassIdW'); {Do not Localize} + Result := WSAGetServiceClassNameByClassIdW(lpServiceClassId, lpszServiceClassName, lpdwBufferLength); +end; + +function Stub_WSAGetServiceClassNameByClassId(const lpServiceClassId: PGUID; + lpszServiceClassName: {$IFDEF UNICODE}PWideChar{$ELSE}PAnsiChar{$ENDIF}; + var lpdwBufferLength: DWORD): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSAGetServiceClassNameByClassId := FixupStub(hWinSockDll, 'WSAGetServiceClassNameByClassIdW'); {Do not Localize} + {$ELSE} + @WSAGetServiceClassNameByClassId := FixupStub(hWinSockDll, 'WSAGetServiceClassNameByClassIdA'); {Do not Localize} + {$ENDIF} + Result := WSAGetServiceClassNameByClassId(lpServiceClassId, lpszServiceClassName, lpdwBufferLength); +end; + +function Stub_WSASetServiceA(const lpqsRegInfo: LPWSAQUERYSETA; const essoperation: WSAESETSERVICEOP; const dwControlFlags: DWORD): Integer; stdcall; +begin + @WSASetServiceA := FixupStub(hWinSockDll, 'WSASetServiceA'); {Do not Localize} + Result := WSASetServiceA(lpqsRegInfo, essoperation, dwControlFlags); +end; + +function Stub_WSASetServiceW(const lpqsRegInfo: LPWSAQUERYSETW; const essoperation: WSAESETSERVICEOP; const dwControlFlags: DWORD): Integer; stdcall; +begin + @WSASetServiceW := FixupStub(hWinSockDll, 'WSASetServiceW'); {Do not Localize} + Result := WSASetServiceW(lpqsRegInfo, essoperation, dwControlFlags); +end; + +function Stub_WSASetService(const lpqsRegInfo: LPWSAQUERYSET; const essoperation: WSAESETSERVICEOP; const dwControlFlags: DWORD): Integer; stdcall; +begin + {$IFDEF UNICODE} + @WSASetService := FixupStub(hWinSockDll, 'WSASetServiceW'); {Do not Localize} + {$ELSE} + @WSASetService := FixupStub(hWinSockDll, 'WSASetServiceA'); {Do not Localize} + {$ENDIF} + Result := WSASetService(lpqsRegInfo, essoperation, dwControlFlags); +end; + +function Stub_WSAProviderConfigChange(var lpNotificationHandle: THandle; AOverlapped: LPWSAOVERLAPPED; lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSAProviderConfigChange := FixupStub(hWinSockDll, 'WSAProviderConfigChange'); {Do not Localize} + Result := WSAProviderConfigChange(lpNotificationHandle, AOverlapped, lpCompletionRoutine); +end; + +function Stub_TransmitFile(hSocket: TSocket; hFile: THandle; nNumberOfBytesToWrite: DWORD; + nNumberOfBytesPerSend: DWORD; lpOverlapped: POverlapped; + lpTransmitBuffers: LPTRANSMIT_FILE_BUFFERS; dwReserved: DWORD): BOOL; stdcall; +begin + @TransmitFile := FixupStubEx(hSocket, 'TransmitFile', WSAID_TRANSMITFILE); {Do not localize} + Result := TransmitFile(hSocket, hFile, nNumberOfBytesToWrite, nNumberOfBytesPerSend, lpOverlapped, lpTransmitBuffers, dwReserved); +end; + +{RLebeau 1/26/2006 - loading GetAcceptExSockaddrs() at the same time as AcceptEx(). +This is because GetAcceptExSockaddrs() is not passed a SOCKET that can be passed to +WSAIoCtrl() to get the function pointer. Also, GetAcceptExSockaddrs() is needed to +parse AcceptEx()'s return data, so there is no point in calling AcceptEx() unless +its data can be parsed afterwards.} +function Stub_AcceptEx(sListenSocket, sAcceptSocket: TSocket; + lpOutputBuffer: Pointer; dwReceiveDataLength, dwLocalAddressLength, + dwRemoteAddressLength: DWORD; var lpdwBytesReceived: DWORD; + lpOverlapped: POverlapped): BOOL; stdcall; +begin + {RLebeau - loading GetAcceptExSockaddrs() first in case it fails} + @GetAcceptExSockaddrs := FixupStubEx(sListenSocket, 'GetAcceptExSockaddrs', WSAID_GETACCEPTEXSOCKADDRS); {Do not localize} + @AcceptEx := FixupStubEx(sListenSocket, 'AcceptEx', WSAID_ACCEPTEX); {Do not localize} + Result := AcceptEx(sListenSocket, sAcceptSocket, lpOutputBuffer, dwReceiveDataLength, + dwLocalAddressLength, dwRemoteAddressLength, lpdwBytesReceived, lpOverlapped); +end; + +{$IFNDEF WINCE} +function Stub_WSARecvEx(s: TSocket; var buf; len: Integer; var flags: Integer): Integer; stdcall; +begin + LoadMSWSock; + @WSARecvEx := FixupStub(hMSWSockDll, 'WSARecvEx'); {Do not localize} + Result := WSARecvEx(s, buf, len, flags); +end; +{$ENDIF} + +function Stub_ConnectEx(const s : TSocket; const name: PSockAddr; const namelen: Integer; lpSendBuffer : Pointer; + dwSendDataLength : DWORD; var lpdwBytesSent : DWORD; lpOverlapped : LPWSAOVERLAPPED) : BOOL; stdcall; +begin + @ConnectEx := FixupStubEx(s, 'ConnectEx', WSAID_CONNECTEX); {Do not localize} + Result := ConnectEx(s, name, namelen, lpSendBuffer, dwSendDataLength, lpdwBytesSent, lpOverlapped); +end; + +function Stub_DisconnectEx(const s : TSocket; AOverlapped: Pointer; const dwFlags : DWord; const dwReserved : DWORD) : BOOL; stdcall; +begin + @DisconnectEx := FixupStubEx(s, 'DisconnectEx', WSAID_DISCONNECTEX); {Do not localize} + Result := DisconnectEx(s, AOverlapped, dwFlags, dwReserved); +end; + +function Stub_WSARecvMsg(const s : TSocket; lpMsg : LPWSAMSG; var lpNumberOfBytesRecvd : DWORD; AOverlapped: Pointer; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; +begin + @WSARecvMsg := FixupStubEx(s, 'WSARecvMsg', WSAID_WSARECVMSG); {Do not localize} + Result := WSARecvMsg(s, lpMsg, lpNumberOfBytesRecvd, AOverlapped, lpCompletionRoutine); +end; + +function Stub_TransmitPackets(s: TSocket; lpPacketArray: LPTRANSMIT_PACKETS_ELEMENT; + nElementCount: DWORD; nSendSize: DWORD; lpOverlapped: LPWSAOVERLAPPED; dwFlags: DWORD): BOOL; stdcall; +begin + @TransmitPackets := FixupStubEx(s, 'TransmitPackets', WSAID_TRANSMITPACKETS); {Do not localize} + Result := TransmitPackets(s, lpPacketArray, nElementCount, nSendSize, lpOverlapped, dwFlags); +end; + +{$IFNDEF WINCE} +function Stub_WSASendMsg(const s : TSocket; lpMsg : LPWSAMSG; const dwFlags : DWORD; var lpNumberOfBytesSent : DWORD; lpOverlapped : LPWSAOVERLAPPED; lpCompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) : Integer; stdcall; +begin + @WSASendMsg := FixupStubEx(s, 'WSASendMsg', WSAID_WSASENDMSG); {Do not localize} + Result := WSASendMsg(s, lpMsg, dwFlags, lpNumberOfBytesSent, lpOverlapped, lpCompletionRoutine); +end; + +function Stub_WSAPoll(fdarray : LPWSAPOLLFD; const nfds : u_long; const timeout : Integer) : Integer; stdcall; +begin + @WSAPoll := FixupStubEx(fdarray.fd, 'WSAPoll', WSAID_WSAPOLL); {Do not localize} + Result := WSAPoll(fdarray, nfds, timeout); +end; +{$ENDIF} + +procedure InitializeStubsEx; +var + LSocket: TSocket; +begin + LSocket := WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nil, 0, WSA_FLAG_OVERLAPPED); + try + @AcceptEx := FixupStubEx(LSocket, 'AcceptEx', WSAID_ACCEPTEX); + @GetAcceptExSockaddrs := FixupStubEx(LSocket, 'GetAcceptExSockaddrs', WSAID_GETACCEPTEXSOCKADDRS); {Do not localize} + @ConnectEx := FixupStubEx(LSocket, 'ConnectEx', WSAID_CONNECTEX); {Do not localize} + @DisconnectEx := FixupStubEx(LSocket, 'DisconnectEx', WSAID_DISCONNECTEX); {Do not localize} + @WSARecvMsg := FixupStubEx(LSocket, 'WSARecvMsg', WSAID_WSARECVMSG); {Do not localize} + @WSARecvMsg := FixupStubEx(LSocket, 'WSARecvMsg', WSAID_WSARECVMSG); {Do not localize} + @TransmitFile := FixupStubEx(LSocket, 'TransmitFile', WSAID_TRANSMITFILE); {Do not localize} + @TransmitPackets := FixupStubEx(LSocket, 'TransmitPackets', WSAID_TRANSMITPACKETS); {Do not localize} + + {$IFNDEF WINCE} +// @WSASendMsg := FixupStubEx(LSocket, 'WSASendMsg', WSAID_WSASENDMSG); {Do not localize} +// @WSAPoll := FixupStubEx(LSocket, 'WSAPoll', WSAID_WSAPOLL); {Do not localize} + {$ENDIF} + finally + closesocket(LSocket); + end; +end; + +procedure InitializeStubs; +{Alphabetize these so we can more easily determine what's available on a platform. +by section in Winsock SDK reference} +begin + accept := Stub_accept; + bind := Stub_bind; + closesocket := Stub_closesocket; + connect := Stub_connect; + ioctlsocket := Stub_ioctlsocket; + getpeername := Stub_getpeername; + getsockname := Stub_getsockname; + getsockopt := Stub_getsockopt; + htonl := Stub_htonl; + htons := Stub_htons; + inet_addr := Stub_inet_addr; + inet_ntoa := Stub_inet_ntoa; + listen := Stub_listen; + ntohl := Stub_ntohl; + ntohs := Stub_ntohs; + recv := Stub_recv; + recvfrom := Stub_recvfrom; + select := Stub_select; + send := Stub_send; + sendto := Stub_sendto; + {$IFDEF WINCE} + sethostname := Stub_sethostname; + {$ENDIF} + setsockopt := Stub_setsockopt; + shutdown := Stub_shutdown; + socket := Stub_socket; + gethostbyaddr := Stub_gethostbyaddr; + gethostbyname := Stub_gethostbyname; + gethostname := Stub_gethostname; + getservbyport := Stub_getservbyport; + getservbyname := Stub_getservbyname; + getprotobynumber := Stub_getprotobynumber; + getprotobyname := Stub_getprotobyname; + //extensions + __WSAFDIsSet := Stub___WSAFDIsSet; + {$IFNDEF WINCE} + AcceptEx := Stub_AcceptEx; + //GetAcceptExSockaddrs is loaded by Stub_AcceptEx + ConnectEx := Stub_ConnectEx; + DisconnectEx := Stub_DisconnectEx; + TransmitFile := Stub_TransmitFile; + TransmitPackets := Stub_TransmitPackets; + {$ENDIF} + WSAAccept := Stub_WSAAccept; + {$IFNDEF WINCE} + WSACancelAsyncRequest := Stub_WSACancelAsyncRequest; + WSAAsyncGetHostByAddr := Stub_WSAAsyncGetHostByAddr; + WSAAsyncGetHostByName := Stub_WSAAsyncGetHostByName; + WSAAsyncGetProtoByName := Stub_WSAAsyncGetProtoByName; + WSAAsyncGetProtoByNumber := Stub_WSAAsyncGetProtoByNumber; + WSAAsyncGetServByName := Stub_WSAAsyncGetServByName; + WSAAsyncGetServByPort := Stub_WSAAsyncGetServByPort; + WSAAsyncSelect := Stub_WSAAsyncSelect; + {$ENDIF} + WSAAddressToStringA := Stub_WSAAddressToStringA; + WSAAddressToStringW := Stub_WSAAddressToStringW; + WSAAddressToString := Stub_WSAAddressToString; + {$IFNDEF WINCE} + WSACancelBlockingCall := Stub_WSACancelBlockingCall; + {$ENDIF} + WSACleanup := Stub_WSACleanup; + WSACloseEvent := Stub_WSACloseEvent; + WSAConnect := Stub_WSAConnect; + WSACreateEvent := Stub_WSACreateEvent; + {$IFNDEF WINCE} + WSADuplicateSocketA := Stub_WSADuplicateSocketA; + WSADuplicateSocketW := Stub_WSADuplicateSocketW; + WSADuplicateSocket := Stub_WSADuplicateSocket; + {$ENDIF} + WSAEnumNameSpaceProvidersA := Stub_WSAEnumNameSpaceProvidersA; + WSAEnumNameSpaceProvidersW := Stub_WSAEnumNameSpaceProvidersW; + WSAEnumNameSpaceProviders := Stub_WSAEnumNameSpaceProviders; + WSAEnumNetworkEvents := Stub_WSAEnumNetworkEvents; + WSAEnumProtocolsA := Stub_WSAEnumProtocolsA; + WSAEnumProtocolsW := Stub_WSAEnumProtocolsW; + WSAEnumProtocols := Stub_WSAEnumProtocols; + WSAEventSelect := Stub_WSAEventSelect; + WSAGetLastError := Stub_WSAGetLastError; + WSAGetOverlappedResult := Stub_WSAGetOverlappedResult; + {$IFNDEF WINCE} + WSAGetQOSByName := Stub_WSAGetQOSByName; + WSAGetServiceClassInfoA := Stub_WSAGetServiceClassInfoA; + WSAGetServiceClassInfoW := Stub_WSAGetServiceClassInfoW; + WSAGetServiceClassInfo := Stub_WSAGetServiceClassInfo; + WSAGetServiceClassNameByClassIdA := Stub_WSAGetServiceClassNameByClassIdA; + WSAGetServiceClassNameByClassIdW := Stub_WSAGetServiceClassNameByClassIdW; + WSAGetServiceClassNameByClassId := Stub_WSAGetServiceClassNameByClassId; + {$ENDIF} + WSAHtonl := Stub_WSAHtonl; + WSAHtons := Stub_WSAHtons; + {$IFNDEF WINCE} + WSAInstallServiceClassA := Stub_WSAInstallServiceClassA; + WSAInstallServiceClassW := Stub_WSAInstallServiceClassW; + WSAInstallServiceClass := Stub_WSAInstallServiceClass; + {$ENDIF} + WSAIoctl := Stub_WSAIoctl; + {$IFNDEF WINCE} + WSAIsBlocking := Stub_WSAIsBlocking; + {$ENDIF} + WSAJoinLeaf := Stub_WSAJoinLeaf; + WSALookupServiceBeginA := Stub_WSALookupServiceBeginA; + WSALookupServiceBeginW := Stub_WSALookupServiceBeginW; + WSALookupServiceBegin := Stub_WSALookupServiceBegin; + WSALookupServiceEnd := Stub_WSALookupServiceEnd; + WSALookupServiceNextA := Stub_WSALookupServiceNextA; + WSALookupServiceNextW := Stub_WSALookupServiceNextW; + WSALookupServiceNext := Stub_WSALookupServiceNext; + + // WSANSPIoctl is not supported in WinCE 4.20 but is in later versions. + WSANSPIoctl := Stub_WSANSPIoctl; + + WSANtohl := Stub_WSANtohl; + WSANtohs := Stub_WSANtohs; + {$IFNDEF WINCE} + WSAPoll := Stub_WSAPoll; + WSAProviderConfigChange := Stub_WSAProviderConfigChange; + {$ENDIF} + WSARecv := Stub_WSARecv; + {$IFNDEF WINCE} + WSARecvDisconnect := Stub_WSARecvDisconnect; + WSARecvEx := Stub_WSARecvEx; + {$ENDIF} + WSARecvFrom := Stub_WSARecvFrom; + WSARecvMsg := Stub_WSARecvMsg; + WSARemoveServiceClass := Stub_WSARemoveServiceClass; + WSAResetEvent := Stub_WSAResetEvent; + WSASend := Stub_WSASend; + {$IFNDEF WINCE} + WSASendDisconnect := Stub_WSASendDisconnect; + WSASendMsg := Stub_WSASendMsg; + {$ENDIF} + WSASendTo := Stub_WSASendTo; + {$IFNDEF WINCE} + WSASetBlockingHook := Stub_WSASetBlockingHook; + {$ENDIF} + WSASetEvent := Stub_WSASetEvent; + WSASetLastError := Stub_WSASetLastError; + WSASetServiceA := Stub_WSASetServiceA; + WSASetServiceW := Stub_WSASetServiceW; + WSASetService := Stub_WSASetService; + WSASocketA := Stub_WSASocketA; + WSASocketW := Stub_WSASocketW; + WSASocket := Stub_WSASocket; + WSAStartup := Stub_WSAStartup; + WSAStringToAddressA := Stub_WSAStringToAddressA; + WSAStringToAddressW := Stub_WSAStringToAddressW; + WSAStringToAddress := Stub_WSAStringToAddress; + {$IFNDEF WINCE} + WSAUnhookBlockingHook := Stub_WSAUnhookBlockingHook; + {$ENDIF} + WSAWaitForMultipleEvents := Stub_WSAWaitForMultipleEvents; +end; + +function WSAMakeSyncReply(Buflen, AError: Word): Longint; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := MakeLong(Buflen, AError); +end; + +function WSAMakeSelectReply(Event, AError: Word): Longint; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := MakeLong(Event, AError); +end; + +function WSAGetAsyncBuflen(Param: Longint): Word; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := LOWORD(Param); +end; + +function WSAGetAsyncError(Param: Longint): Word; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := HIWORD(Param); +end; + +function WSAGetSelectEvent(Param: Longint): Word; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := LOWORD(Param); +end; + +function WSAGetSelectError(Param: Longint): Word; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + WSAGetSelectError := HIWORD(Param); +end; + +procedure FD_CLR(ASocket: TSocket; var FDSet: TFDSet); +var + i: u_int; +begin + i := 0; + while i < FDSet.fd_count do + begin + if FDSet.fd_array[i] = ASocket then + begin + while i < FDSet.fd_count - 1 do + begin + FDSet.fd_array[i] := FDSet.fd_array[i+1]; + Inc(i); + end; + Dec(FDSet.fd_count); + Break; + end; + Inc(i); + end; +end; + +function FD_ISSET(ASocket: TSocket; var FDSet: TFDSet): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := __WSAFDIsSet(ASocket, FDSet); +end; + +procedure FD_SET(ASocket: TSocket; var FDSet: TFDSet); +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if FDSet.fd_count < fd_setsize then + begin + FDSet.fd_array[FDSet.fd_count] := ASocket; + Inc(FDSet.fd_count); + end; +end; + +procedure FD_ZERO(var FDSet: TFDSet); +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + FDSet.fd_count := 0; +end; + +//Posix aliases +// #define CMSGHDR_ALIGN WSA_CMSGHDR_ALIGN +function CMSGHDR_ALIGN(const Alength: SIZE_T): SIZE_T; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSGHDR_ALIGN(Alength); +end; + +// #define CMSGDATA_ALIGN WSA_CMSGDATA_ALIGN +function CMSGDATA_ALIGN(const Alength: UINT_PTR): UINT_PTR; + {$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSGDATA_ALIGN(Alength); +end; + +//#define CMSG_FIRSTHDR WSA_CMSG_FIRSTHDR +function CMSG_FIRSTHDR(const msg: LPWSAMSG): LPWSACMSGHDR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSG_FIRSTHDR(msg); +end; + +// #define CMSG_NXTHDR WSA_CMSG_NXTHDR +function CMSG_NXTHDR(const msg: LPWSAMSG; const cmsg: LPWSACMSGHDR): LPWSACMSGHDR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSG_NXTHDR(msg, cmsg); +end; + +// #define CMSG_SPACE WSA_CMSG_SPACE +function CMSG_SPACE(const Alength: UINT_PTR): UINT_PTR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSG_SPACE(ALength); +end; + +// #define CMSG_LEN WSA_CMSG_LEN +function CMSG_LEN(const Alength: SIZE_T): SIZE_T; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSG_LEN(ALength); +end; + +// +function WSA_CMSGHDR_ALIGN(const Alength: SIZE_T): SIZE_T; +type + {$IFDEF WIN32} + {$ALIGN ON} + TempRec = record + x: AnsiChar; + test: WSACMSGHDR; + end; + {$ALIGN OFF} + {$ELSE} + //Win64 and WinCE seem to require alignment for API records + TempRec = record + x: AnsiChar; + test: WSACMSGHDR; + end; + {$ENDIF} +var + Alignment: SIZE_T; + Tmp: ^TempRec; +begin + Tmp := nil; + Alignment := UINT_PTR(@(Tmp^.test)); + Result := (Alength + (Alignment-1)) and not (Alignment-1); +end; + +function WSA_CMSGDATA_ALIGN(const Alength: UINT_PTR): UINT_PTR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := (Alength + MAX_NATURAL_ALIGNMENT_SUB_1) and not (MAX_NATURAL_ALIGNMENT_SUB_1); +end; + +function WSA_CMSG_FIRSTHDR(const msg: LPWSAMSG): LPWSACMSGHDR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if (msg <> nil) and (msg^.Control.len >= SIZE_WSACMSGHDR) then begin + Result := LPWSACMSGHDR(msg^.Control.buf); + end else begin + Result := nil; + end; +end; + +function WSA_CMSG_NXTHDR(const msg: LPWSAMSG; const cmsg: LPWSACMSGHDR): LPWSACMSGHDR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if cmsg = nil then begin + Result := WSA_CMSG_FIRSTHDR(msg); + end else begin + if (UINT_PTR(cmsg) + WSA_CMSGHDR_ALIGN(cmsg^.cmsg_len) + SIZE_WSACMSGHDR) > (UINT_PTR(msg^.Control.buf) + msg^.Control.len) then begin + Result := nil; + end else begin + Result := LPWSACMSGHDR(UINT_PTR(cmsg) + WSA_CMSGHDR_ALIGN(cmsg^.cmsg_len)); + end; + end; +end; + +function WSA_CMSG_DATA(const cmsg: LPWSACMSGHDR): PByte; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := PByte(UINT_PTR(cmsg) + WSA_CMSGDATA_ALIGN(SIZE_WSACMSGHDR)); +end; + +function WSA_CMSG_SPACE(const Alength: UINT_PTR): UINT_PTR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := WSA_CMSGDATA_ALIGN(UINT_PTR(SIZE_WSACMSGHDR + WSA_CMSGHDR_ALIGN(Alength))); +end; + +function WSA_CMSG_LEN(const Alength: SIZE_T): SIZE_T; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := (WSA_CMSGDATA_ALIGN(SizeOf(WSACMSGHDR)) + Alength); +end; + +function IP_MSFILTER_SIZE(const numsrc: DWORD): UINT_PTR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := SIZE_IP_MSFILTER - SIZE_TINADDR + (numsrc*SIZE_TINADDR); +end; + +function SS_PORT(ssp: PSockAddrIn): u_short; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if ssp <> nil then begin + Result := ssp^.sin_port; + end else begin + Result := 0; + end; +end; + +function IN6ADDR_ANY_INIT: TIn6Addr; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + with Result do begin + System.FillChar(s6_addr, SIZE_TIN6ADDR, 0); {Do not Localize} + end; +end; + +function IN6ADDR_LOOPBACK_INIT: TIn6Addr; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + with Result do begin + System.FillChar(s6_addr, SIZE_TIN6ADDR, 0); {Do not Localize} + s6_addr[15] := 1; + end; +end; + +procedure IN6ADDR_SETANY(sa: PSockAddrIn6); +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if sa <> nil then begin + with sa^ do begin + sin6_family := AF_INET6; + sin6_port := 0; + sin6_flowinfo := 0; + PULONG(@sin6_addr.s6_addr[0])^ := 0; + PULONG(@sin6_addr.s6_addr[4])^ := 0; + PULONG(@sin6_addr.s6_addr[8])^ := 0; + PULONG(@sin6_addr.s6_addr[12])^ := 0; + end; + end; +end; + +procedure IN6ADDR_SETLOOPBACK(sa: PSockAddrIn6); +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if sa <> nil then begin + with sa^ do begin + sin6_family := AF_INET6; + sin6_port := 0; + sin6_flowinfo := 0; + PULONG(@sin6_addr.s6_addr[0])^ := 0; + PULONG(@sin6_addr.s6_addr[4])^ := 0; + PULONG(@sin6_addr.s6_addr[8])^ := 0; + PULONG(@sin6_addr.s6_addr[12])^ := 1; + end; + end; +end; + +function IN6ADDR_ISANY(sa: PSockAddrIn6): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if sa <> nil then begin + with sa^ do begin + Result := (sin6_family = AF_INET6) and + (PULONG(@sin6_addr.s6_addr[0])^ = 0) and + (PULONG(@sin6_addr.s6_addr[4])^ = 0) and + (PULONG(@sin6_addr.s6_addr[8])^ = 0) and + (PULONG(@sin6_addr.s6_addr[12])^ = 0); + end; + end else begin + Result := False; + end; +end; + +function IN6ADDR_ISLOOPBACK(sa: PSockAddrIn6): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if sa <> nil then begin + with sa^ do begin + Result := (sin6_family = AF_INET6) and + (PULONG(@sin6_addr.s6_addr[0])^ = 0) and + (PULONG(@sin6_addr.s6_addr[4])^ = 0) and + (PULONG(@sin6_addr.s6_addr[8])^ = 0) and + (PULONG(@sin6_addr.s6_addr[12])^ = 1); + end; + end else begin + Result := False; + end; +end; + +function IN6_ADDR_EQUAL(const a: PIn6Addr; const b: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := System.SysUtils.CompareMem(a, b, SIZE_TIN6ADDR); +end; + +function IN6_IS_ADDR_UNSPECIFIED(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := IN6_ADDR_EQUAL(a, @in6addr_any); +end; + +function IN6_IS_ADDR_LOOPBACK(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := IN6_ADDR_EQUAL(a, @in6addr_loopback); +end; + +function IN6_IS_ADDR_MULTICAST(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := (a^.s6_addr[0] = $FF); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_EUI64(const a : PIn6Addr) : Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} + // + // Format prefixes 001 through 111, except for multicast. + // +begin + if a <> nil then begin + Result := ((a^.s6_addr[0] and $e0) <> 0 ) and (not IN6_IS_ADDR_MULTICAST(a)); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_SUBNET_ROUTER_ANYCAST(const a : PIn6Addr) : Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +// +// Is this the subnet router anycast address? +// See RFC 2373. +// +begin + if a <> nil then begin + Result := IN6_IS_ADDR_EUI64(a) and (a^.word[4] = 0) and + (a^.word[5] = 0) and (a^.word[6]=0) and (a^.word[7]=0); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_SUBNET_RESERVED_ANYCAST(const a: PIn6Addr) : Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_EUI64(a) and (a^.word[4] = $fffd) and + (a^.word[5] = $ffff) and (a^.word[6] = $ffff) and + ((a^.word[7] and $80ff) = $80ff); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_ANYCAST(const a: PIn6Addr) : Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_SUBNET_RESERVED_ANYCAST(a) or IN6_IS_ADDR_SUBNET_ROUTER_ANYCAST(a); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_LINKLOCAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := (a^.s6_addr[0] = $FE) and ((a^.s6_addr[1] and $C0) = $80); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_SITELOCAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := (a^.s6_addr[0] = $FE) and ((a^.s6_addr[1] and $C0) = $C0); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_V4MAPPED(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + with a^ do begin + Result := (word[0] = 0) and + (word[1] = 0) and + (word[2] = 0) and + (word[3] = 0) and + (word[4] = 0) and + (word[5] = $FFFF); + end; + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_V4COMPAT(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + with a^ do begin + Result := (word[0] = 0) and + (word[1] = 0) and + (word[2] = 0) and + (word[3] = 0) and + (word[4] = 0) and + (word[5] = 0) and + not ((word[6] = 0) and (s6_addr[14] = 0) and + ((s6_addr[15] = 0) or (s6_addr[15] = 1))); + end; + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_MC_NODELOCAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_MULTICAST(a) and ((a^.s6_addr[1] and $F) = 1); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_MC_LINKLOCAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_MULTICAST(a) and ((a^.s6_addr[1] and $F) = 2); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_MC_SITELOCAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_MULTICAST(a) and ((a^.s6_addr[1] and $F) = 5); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_MC_ORGLOCAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_MULTICAST(a) and ((a^.s6_addr[1] and $F) = 8); + end else begin + Result := False; + end; +end; + +function IN6_IS_ADDR_MC_GLOBAL(const a: PIn6Addr): Boolean; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + if a <> nil then begin + Result := IN6_IS_ADDR_MULTICAST(a) and ((a^.s6_addr[1] and $F) = $E); + end else begin + Result := False; + end; +end; + +procedure IN6_SET_ADDR_UNSPECIFIED(a : PIN6_ADDR); +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + system.FillChar(a^.s6_addr, SizeOf(IN6_ADDR), 0 ); +end; + +procedure IN6_SET_ADDR_LOOPBACK(a : PIN6_ADDR); +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + system.FillChar(a^.s6_addr, SizeOf(IN6_ADDR), 0 ); + a^.s6_addr[15] := 1; +end; + +// A macro convenient for setting up NETBIOS SOCKADDRs. +procedure SET_NETBIOS_SOCKADDR(snb : PSockAddrNB; const SnbType : Word; const Name : PAnsiChar; const Port : AnsiChar); +var + {$IFDEF FPC} + len : sizeint; + {$ELSE} + len : DWord; + {$ENDIF} +begin + if snb <> nil then begin + with snb^ do begin + snb_family := AF_NETBIOS; + snb_type := SnbType; + len := System.AnsiStrings.StrLen(Name); + if len >= NETBIOS_NAME_LENGTH-1 then begin + System.Move(Name^, snb_name, NETBIOS_NAME_LENGTH-1); + end else begin + if len > 0 then begin + System.Move(Name^, snb_name, LongInt(len)); + end; + System.FillChar((PAnsiChar(@snb_name)+len)^, NETBIOS_NAME_LENGTH-1-len, ' '); {Do not Localize} + end; + snb_name[NETBIOS_NAME_LENGTH-1] := Port; + end; + end; +end; + +function GROUP_FILTER_SIZE(const numsrc : DWord) : UINT_PTR; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + Result := (SIZE_GROUP_FILTER - SIZE_SOCKADDR_STORAGE) + + (numsrc * SIZE_SOCKADDR_STORAGE); +end; + +initialization + in6addr_any := IN6ADDR_ANY_INIT; + in6addr_loopback := IN6ADDR_LOOPBACK_INIT; + InitializeStubs; + InitializeWinSock; + InitializeStubsEx; + +finalization + UninitializeWinSock; + +end. + diff --git a/ThirdParty/DCS/Net/Net.Wship6.pas b/ThirdParty/DCS/Net/Net.Wship6.pas new file mode 100644 index 00000000..0f93507b --- /dev/null +++ b/ThirdParty/DCS/Net/Net.Wship6.pas @@ -0,0 +1,590 @@ +{ + $Project$ + $Workfile$ + $Revision$ + $DateUTC$ + $Id$ + + This file is part of the Indy (Internet Direct) project, and is offered + under the dual-licensing agreement described on the Indy website. + (http://www.indyproject.org/) + + Copyright: + (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. +} +{ + $Log$ +} +{ + Rev 1.0 2004.02.03 3:14:52 PM czhower + Move and updates + + Rev 1.2 10/15/2003 9:43:20 PM DSiders + Added localization comments. + + Rev 1.1 1-10-2003 19:44:28 BGooijen + fixed leak in CloseLibrary() + + Rev 1.0 11/13/2002 09:03:24 AM JPMugaas +} + +unit Net.Wship6; + +interface + +{$I Net.Winsock.inc} + +{$IFDEF FPC} + {$IFDEF WIN32} + {$ALIGN OFF} + {$ELSE} + //It turns out that Win64 and WinCE require record alignment + {$PACKRECORDS C} + {$ENDIF} +{$ELSE} + {$IFDEF WIN64} + {$ALIGN ON} + {$MINENUMSIZE 4} + {$ELSE} + {$MINENUMSIZE 4} + {$IFDEF REQUIRES_PROPER_ALIGNMENT} + {$ALIGN ON} + {$ELSE} + {$ALIGN OFF} + {$WRITEABLECONST OFF} + {$ENDIF} + {$ENDIF} +{$ENDIF} + +uses + {$IFDEF HAS_TInterlocked} + syncobjs, //here to facilitate inlining with Delphi + {$ENDIF} + Windows, + Net.Winsock2; + +const + Wship6_dll = 'Wship6.dll'; {do not localize} + iphlpapi_dll = 'iphlpapi.dll'; {do not localize} + fwpuclnt_dll = 'Fwpuclnt.dll'; {Do not localize} + + // Error codes from getaddrinfo(). + + //JPM + //Note that I am adding a GIA_ prefix on my own because + //some names here share some names defined in Iocp.Winsock2 causing + //an unpredictible problem. The values are not defined the same in Iocp.Winsock2 + {$EXTERNALSYM GIA_EAI_ADDRFAMILY} + GIA_EAI_ADDRFAMILY = 1 ; // Address family for nodename not supported. + {$EXTERNALSYM GIA_EAI_AGAIN} + GIA_EAI_AGAIN = 2 ; // Temporary failure in name resolution. + {$EXTERNALSYM GIA_EAI_BADFLAGS} + GIA_EAI_BADFLAGS = 3 ; // Invalid value for ai_flags. + {$EXTERNALSYM GIA_EAI_FAIL} + GIA_EAI_FAIL = 4 ; // Non-recoverable failure in name resolution. + {$EXTERNALSYM GIA_EAI_FAMILY} + GIA_EAI_FAMILY = 5 ; // Address family ai_family not supported. + {$EXTERNALSYM GIA_EAI_MEMORY} + GIA_EAI_MEMORY = 6 ; // Memory allocation failure. + {$EXTERNALSYM GIA_EAI_NODATA} + GIA_EAI_NODATA = 7 ; // No address associated with nodename. + {$EXTERNALSYM GIA_EAI_NONAME} + GIA_EAI_NONAME = 8 ; // Nodename nor servname provided, or not known. + {$EXTERNALSYM GIA_EAI_SERVICE} + GIA_EAI_SERVICE = 9 ; // Servname not supported for ai_socktype. + {$EXTERNALSYM GIA_EAI_SOCKTYPE} + GIA_EAI_SOCKTYPE = 10 ; // Socket type ai_socktype not supported. + {$EXTERNALSYM GIA_EAI_SYSTEM} + GIA_EAI_SYSTEM = 11 ; // System error returned in errno. + + {$EXTERNALSYM NI_MAXHOST} + NI_MAXHOST = 1025; // Max size of a fully-qualified domain name. + {$EXTERNALSYM NI_MAXSERV} + NI_MAXSERV = 32; // Max size of a service name. + + // Flags for getnameinfo(). + + {$EXTERNALSYM NI_NOFQDN} + NI_NOFQDN = $1 ; // Only return nodename portion for local hosts. + {$EXTERNALSYM NI_NUMERICHOST} + NI_NUMERICHOST = $2 ; // Return numeric form of the host's address. + {$EXTERNALSYM NI_NAMEREQD} + NI_NAMEREQD = $4 ; // Error if the host's name not in DNS. + {$EXTERNALSYM NI_NUMERICSERV} + NI_NUMERICSERV = $8 ; // Return numeric form of the service (port #). + {$EXTERNALSYM NI_DGRAM} + NI_DGRAM = $10 ; // Service is a datagram service. + + //JPM - These may not be supported in WinCE 4.2 + {$EXTERNALSYM PROTECTION_LEVEL_RESTRICTED} + PROTECTION_LEVEL_RESTRICTED = 30; //* for Intranet apps /* + {$EXTERNALSYM PROTECTION_LEVEL_DEFAULT} + PROTECTION_LEVEL_DEFAULT = 20; //* default level /* + {$EXTERNALSYM PROTECTION_LEVEL_UNRESTRICTED} + PROTECTION_LEVEL_UNRESTRICTED = 10; //* for peer-to-peer apps /* + + {$EXTERNALSYM SOCKET_SETTINGS_GUARANTEE_ENCRYPTION} + SOCKET_SETTINGS_GUARANTEE_ENCRYPTION = $00000001; + {$EXTERNALSYM SOCKET_SETTINGS_ALLOW_INSECURE} + SOCKET_SETTINGS_ALLOW_INSECURE = $00000002; + + {$EXTERNALSYM SOCKET_INFO_CONNECTION_SECURED} + SOCKET_INFO_CONNECTION_SECURED = $00000001; + {$EXTERNALSYM SOCKET_INFO_CONNECTION_ENCRYPTED} + SOCKET_INFO_CONNECTION_ENCRYPTED = $00000002; + +type + // RLebeau: find a better place for this + {$IFNDEF HAS_UInt64} + {$EXTERNALSYM UINT64} + UINT64 = Int64; + {$ENDIF} + + {$NODEFINE PPaddrinfo} + PPaddrinfo = ^PAddrInfo; + {$NODEFINE PPaddrinfoW} + PPaddrinfoW = ^PAddrInfoW; + + {$IFNDEF WINCE} + {$EXTERNALSYM SOCKET_SECURITY_PROTOCOL} + {$EXTERNALSYM SOCKET_SECURITY_PROTOCOL_DEFAULT} + {$EXTERNALSYM SOCKET_SECURITY_PROTOCOL_IPSEC} + {$EXTERNALSYM SOCKET_SECURITY_PROTOCOL_INVALID} + SOCKET_SECURITY_PROTOCOL = ( + SOCKET_SECURITY_PROTOCOL_DEFAULT, SOCKET_SECURITY_PROTOCOL_IPSEC, SOCKET_SECURITY_PROTOCOL_INVALID + ); + + {$EXTERNALSYM SOCKET_SECURITY_SETTINGS_IPSEC} + SOCKET_SECURITY_SETTINGS_IPSEC = record + SecurityProtocol : SOCKET_SECURITY_PROTOCOL; + SecurityFlags : ULONG; + IpsecFlags : ULONG; + AuthipMMPolicyKey : TGUID; + AuthipQMPolicyKey : TGUID; + Reserved : TGUID; + Reserved2 : UINT64; + UserNameStringLen : ULONG; + DomainNameStringLen : ULONG; + PasswordStringLen : ULONG; + // wchar_t AllStrings[0]; + end; + {$EXTERNALSYM PSOCKET_SECURITY_SETTINGS_IPSEC} + PSOCKET_SECURITY_SETTINGS_IPSEC = ^SOCKET_SECURITY_SETTINGS_IPSEC; + + {$EXTERNALSYM SOCKET_PEER_TARGET_NAME} + SOCKET_PEER_TARGET_NAME = record + SecurityProtocol : SOCKET_SECURITY_PROTOCOL; + PeerAddress : SOCKADDR_STORAGE; + PeerTargetNameStringLen : ULONG; + //wchar_t AllStrings[0]; + end; + {$EXTERNALSYM PSOCKET_PEER_TARGET_NAME} + PSOCKET_PEER_TARGET_NAME = ^SOCKET_PEER_TARGET_NAME; + + {$EXTERNALSYM SOCKET_SECURITY_QUERY_INFO} + SOCKET_SECURITY_QUERY_INFO = record + SecurityProtocol : SOCKET_SECURITY_PROTOCOL; + Flags : ULONG; + PeerApplicationAccessTokenHandle : UINT64; + PeerMachineAccessTokenHandle : UINT64; + end; + {$EXTERNALSYM PSOCKET_SECURITY_QUERY_INFO} + PSOCKET_SECURITY_QUERY_INFO = ^SOCKET_SECURITY_QUERY_INFO; + {$EXTERNALSYM SOCKET_SECURITY_QUERY_TEMPLATE} + SOCKET_SECURITY_QUERY_TEMPLATE = record + SecurityProtocol : SOCKET_SECURITY_PROTOCOL; + PeerAddress : SOCKADDR_STORAGE; + PeerTokenAccessMask : ULONG; + end; + {$EXTERNALSYM PSOCKET_SECURITY_QUERY_TEMPLATE} + PSOCKET_SECURITY_QUERY_TEMPLATE = ^SOCKET_SECURITY_QUERY_TEMPLATE; + +//callback defs +type + {$EXTERNALSYM LPLOOKUPSERVICE_COMPLETION_ROUTINE} + LPLOOKUPSERVICE_COMPLETION_ROUTINE = procedure (const dwError, dwBytes : DWORD; lpOverlapped : LPWSAOVERLAPPED); stdcall; +{$ENDIF} + +type + {$EXTERNALSYM LPFN_GETADDRINFO} + LPFN_GETADDRINFO = function(NodeName: PAnsiChar; ServiceName: PAnsiChar; Hints: Paddrinfo; ppResult: PPaddrinfo): Integer; stdcall; + {$EXTERNALSYM LPFN_GETADDRINFOW} + LPFN_GETADDRINFOW = function(NodeName: PWideChar; ServiceName: PWideChar; Hints: PaddrinfoW; ppResult: PPaddrinfoW): Integer; stdcall; + {$EXTERNALSYM LPFN_GETNAMEINFO} + //The IPv6 preview for Win2K defines hostlen and servelen as size_t but do not use them + //for these definitions as the newer SDK's define those as DWORD. + LPFN_GETNAMEINFO = function(sa: psockaddr; salen: u_int; host: PAnsiChar; hostlen: u_int; serv: PAnsiChar; servlen: u_int; flags: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_GETNAMEINFOW} + LPFN_GETNAMEINFOW = function(sa: psockaddr; salen: u_int; host: PWideChar; hostlen: u_int; serv: PWideChar; servlen: u_int; flags: Integer): Integer; stdcall; + {$EXTERNALSYM LPFN_FREEADDRINFO} + LPFN_FREEADDRINFO = procedure(ai: Paddrinfo); stdcall; + {$EXTERNALSYM LPFN_FREEADDRINFOW} + LPFN_FREEADDRINFOW = procedure(ai: PaddrinfoW); stdcall; + +//function GetAdaptersAddresses( Family:cardinal; Flags:cardinal; Reserved:pointer; pAdapterAddresses: PIP_ADAPTER_ADDRESSES; pOutBufLen:pcardinal):cardinal;stdcall; external iphlpapi_dll; + +{ the following are not used, nor tested} +{function getipnodebyaddr(const src:pointer; len:integer; af:integer;var error_num:integer) :phostent;stdcall; external Wship6_dll; +procedure freehostent(ptr:phostent);stdcall; external Wship6_dll; +function inet_pton(af:integer; const src:pchar; dst:pointer):integer;stdcall; external Wship6_dll; +function inet_ntop(af:integer; const src:pointer; dst:pchar;size:integer):pchar;stdcall; external Wship6_dll; +} + {$IFNDEF WINCE} + {$EXTERNALSYM LPFN_INET_PTON} + LPFN_INET_PTON = function (af: Integer; const src: PAnsiChar; dst: Pointer): Integer; stdcall; + {$EXTERNALSYM LPFN_INET_PTONW} + LPFN_INET_PTONW = function (af: Integer; const src: PWideChar; dst: Pointer): Integer; stdcall; + {$EXTERNALSYM LPFN_INET_NTOP} + LPFN_INET_NTOP = function (af: Integer; const src: Pointer; dst: PAnsiChar; size: size_t): PAnsiChar; stdcall; + {$EXTERNALSYM LPFN_INET_NTOPW} + LPFN_INET_NTOPW = function (af: Integer; const src: Pointer; dst: PWideChar; size: size_t): PAnsiChar; stdcall; + +{ end the following are not used, nor tested} +//These are provided in case we need them later +//Windows Vista + {$EXTERNALSYM LPFN_GETADDRINFOEXA} + LPFN_GETADDRINFOEXA = function(pName : PAnsiChar; pServiceName : PAnsiChar; + const dwNameSpace: DWord; lpNspId : LPGUID; hints : PADDRINFOEXA; + ppResult : PADDRINFOEXA; timeout : Ptimeval; lpOverlapped : LPWSAOVERLAPPED; + lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE; + var lpNameHandle : THandle) : Integer; stdcall; + {$EXTERNALSYM LPFN_GETADDRINFOEXW} + LPFN_GETADDRINFOEXW = function(pName : PWideChar; pServiceName : PWideChar; + const dwNameSpace: DWord; lpNspId : LPGUID;hints : PADDRINFOEXW; + ppResult : PADDRINFOEXW; timeout : Ptimeval; lpOverlapped : LPWSAOVERLAPPED; + lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE; + var lpNameHandle : THandle) : Integer; stdcall; + {$EXTERNALSYM LPFN_SETADDRINFOEXA} + LPFN_SETADDRINFOEXA= function(pName : PAnsiChar; pServiceName : PAnsiChar; + pAddresses : PSOCKET_ADDRESS; const dwAddressCount : DWord; lpBlob : LPBLOB; + const dwFlags : DWord; const dwNameSpace : DWord; lpNspId : LPGUID; + timeout : Ptimeval; + lpOverlapped : LPWSAOVERLAPPED; + lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE; var lpNameHandle : THandle) : Integer; stdcall; + {$EXTERNALSYM LPFN_SETADDRINFOEXW} + LPFN_SETADDRINFOEXW= function(pName : PWideChar; pServiceName : PWideChar; + pAddresses : PSOCKET_ADDRESS; const dwAddressCount : DWord; lpBlob : LPBLOB; + const dwFlags : DWord; const dwNameSpace : DWord; lpNspId : LPGUID; + timeout : Ptimeval; + lpOverlapped : LPWSAOVERLAPPED; + lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE; var lpNameHandle : THandle) : Integer; stdcall; + + {$EXTERNALSYM LPFN_FREEADDRINFOEX} + LPFN_FREEADDRINFOEX = procedure(pAddrInfoEx : PADDRINFOEXA) ; stdcall; + {$EXTERNALSYM LPFN_FREEADDRINFOEXW} + LPFN_FREEADDRINFOEXW = procedure(pAddrInfoEx : PADDRINFOEXW) ; stdcall; + + {$EXTERNALSYM LPFN_GETADDRINFOEX} + {$EXTERNALSYM LPFN_SETADDRINFOEX} + {$IFDEF UNICODE} + LPFN_GETADDRINFOEX = LPFN_GETADDRINFOEXW; + LPFN_SETADDRINFOEX = LPFN_SETADDRINFOEXW; + {$ELSE} + LPFN_GETADDRINFOEX = LPFN_GETADDRINFOEXA; + LPFN_SETADDRINFOEX = LPFN_SETADDRINFOEXA; + {$ENDIF} + + // Fwpuclnt.dll - API + {$EXTERNALSYM LPFN_WSADELETESOCKETPEERTARGETNAME} + LPFN_WSADELETESOCKETPEERTARGETNAME = function (Socket : TSocket; + PeerAddr : Psockaddr; PeerAddrLen : ULONG; + Overlapped : LPWSAOVERLAPPED; CompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer; stdcall; + {$EXTERNALSYM LPFN_WSASETSOCKETPEERTARGETNAME} + LPFN_WSASETSOCKETPEERTARGETNAME = function (Socket : TSocket; + PeerTargetName : PSOCKET_PEER_TARGET_NAME; PeerTargetNameLen : ULONG; + Overlapped : LPWSAOVERLAPPED; CompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAIMPERSONATESOCKETPEER} + LPFN_WSAIMPERSONATESOCKETPEER = function (Socket : TSocket; + PeerAddress : Psockaddr; peerAddressLen : ULONG) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAQUERYSOCKETSECURITY} + LPFN_WSAQUERYSOCKETSECURITY = function (Socket : TSocket; + SecurityQueryTemplate : PSOCKET_SECURITY_QUERY_TEMPLATE; const SecurityQueryTemplateLen : ULONG; + var SecurityQueryInfo : PSOCKET_SECURITY_QUERY_INFO; var SecurityQueryInfoLen : ULONG; + Overlapped : LPWSAOVERLAPPED; CompletionRoutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) : Integer; stdcall; + {$EXTERNALSYM LPFN_WSAREVERTIMPERSONATION} + LPFN_WSAREVERTIMPERSONATION = function : Integer; stdcall; +{$ENDIF} + +const + {$NODEFINE fn_GetAddrInfoEx} + {$NODEFINE fn_SetAddrInfoEx} + {$NODEFINE fn_FreeAddrInfoEx} + {$NODEFINE fn_GetAddrInfo} + {$NODEFINE fn_getnameinfo} + {$NODEFINE fn_freeaddrinfo} + {$NODEFINE fn_inet_pton} + {$NODEFINE fn_inet_ntop} + {$IFDEF UNICODE} + {$IFNDEF WINCE} + fn_GetAddrInfoEx = 'GetAddrInfoExW'; + fn_SetAddrInfoEx = 'SetAddrInfoExW'; + fn_FreeAddrInfoEx = 'FreeAddrInfoExW'; + {$ENDIF} + fn_GetAddrInfo = 'GetAddrInfoW'; + fn_getnameinfo = 'GetNameInfoW'; + fn_freeaddrinfo = 'FreeAddrInfoW'; + {$IFNDEF WINCE} + fn_inet_pton = 'InetPtonW'; + fn_inet_ntop = 'InetNtopW'; + {$ENDIF} + {$ELSE} + {$IFNDEF WINCE} + fn_GetAddrInfoEx = 'GetAddrInfoExA'; + fn_SetAddrInfoEx = 'SetAddrInfoExA'; + fn_FreeAddrInfoEx = 'FreeAddrInfoEx'; + {$ENDIF} + fn_GetAddrInfo = 'getaddrinfo'; + fn_getnameinfo = 'getnameinfo'; + fn_freeaddrinfo = 'freeaddrinfo'; + {$IFNDEF WINCE} + fn_inet_pton = 'inet_pton'; + fn_inet_ntop = 'inet_ntop'; + {$ENDIF} + {$ENDIF} + +var + {$EXTERNALSYM getaddrinfo} + {$EXTERNALSYM getnameinfo} + {$EXTERNALSYM freeaddrinfo} + {$EXTERNALSYM inet_pton} + {$EXTERNALSYM inet_ntop} + {$IFDEF UNICODE} + getaddrinfo: LPFN_GETADDRINFOW = nil; + getnameinfo: LPFN_GETNAMEINFOW = nil; + freeaddrinfo: LPFN_FREEADDRINFOW = nil; + {$IFNDEF WINCE} + //These are here for completeness + inet_pton : LPFN_inet_ptonW = nil; + inet_ntop : LPFN_inet_ntopW = nil; + {$ENDIF} + {$ELSE} + getaddrinfo: LPFN_GETADDRINFO = nil; + getnameinfo: LPFN_GETNAMEINFO = nil; + freeaddrinfo: LPFN_FREEADDRINFO = nil; + {$IFNDEF WINCE} + //These are here for completeness + inet_pton : LPFN_inet_pton = nil; + inet_ntop : LPFN_inet_ntop = nil; + {$ENDIF} + {$ENDIF} + {$IFNDEF WINCE} + { + IMPORTANT!!! + + These are Windows Vista functions and there's no guarantee that you will have + them so ALWAYS check the function pointer before calling them. + } + {$EXTERNALSYM GetAddrInfoEx} + GetAddrInfoEx : LPFN_GETADDRINFOEX = nil; + {$EXTERNALSYM SetAddrInfoEx} + SetAddrInfoEx : LPFN_SETADDRINFOEX = nil; + {$EXTERNALSYM FreeAddrInfoEx} + //You can't alias the LPFN for this because the ASCII version of this + //does not end with an "a" + {$IFDEF UNICODE} + FreeAddrInfoEx : LPFN_FREEADDRINFOEX = nil; + {$ELSE} + FreeAddrInfoEx : LPFN_FREEADDRINFOEXW = nil; + {$ENDIF} + + //Fwpuclnt.dll available for Windows Vista and later + {$EXTERNALSYM WSASETSOCKETPEERTARGETNAME} + WSASetSocketPeerTargetName : LPFN_WSASETSOCKETPEERTARGETNAME = nil; + {$EXTERNALSYM WSADELETESOCKETPEERTARGETNAME} + WSADeleteSocketPeerTargetName : LPFN_WSADELETESOCKETPEERTARGETNAME = nil; + {$EXTERNALSYM WSAImpersonateSocketPeer} + WSAImpersonateSocketPeer : LPFN_WSAIMPERSONATESOCKETPEER = nil; + {$EXTERNALSYM WSAQUERYSOCKETSECURITY} + WSAQUERYSOCKETSECURITY : LPFN_WSAQUERYSOCKETSECURITY = nil; + {$EXTERNALSYM WSAREVERTIMPERSONATION} + WSARevertImpersonation : LPFN_WSAREVERTIMPERSONATION = nil; + {$ENDIF} + +var + GIdIPv6FuncsAvailable: Boolean = False; + +function gaiErrorToWsaError(const gaiError: Integer): Integer; + +//We want to load this library only after loading Winsock and unload immediately +//before unloading Winsock. +procedure InitLibrary; +procedure CloseLibrary; + +implementation + +uses + SysUtils; + +var + hWship6Dll : THandle = 0; // Wship6.dll handle + //Use this instead of hWship6Dll because this will point to the correct lib. + hProcHandle : THandle = 0; + {$IFNDEF WINCE} + hfwpuclntDll : THandle = 0; + {$ENDIF} + +function gaiErrorToWsaError(const gaiError: Integer): Integer; +begin + case gaiError of + GIA_EAI_ADDRFAMILY: Result := 0; + GIA_EAI_AGAIN: Result := WSATRY_AGAIN; + GIA_EAI_BADFLAGS: Result := WSAEINVAL; + GIA_EAI_FAIL: Result := WSANO_RECOVERY; + GIA_EAI_FAMILY: Result := WSAEAFNOSUPPORT; + GIA_EAI_MEMORY: Result := WSA_NOT_ENOUGH_MEMORY; + GIA_EAI_NODATA: Result := WSANO_DATA; + GIA_EAI_NONAME: Result := WSAHOST_NOT_FOUND; + GIA_EAI_SERVICE: Result := WSATYPE_NOT_FOUND; + GIA_EAI_SOCKTYPE: Result := WSAESOCKTNOSUPPORT; + GIA_EAI_SYSTEM: + begin + Result := 0; // avoid warning + RaiseLastOSError; + end; + else + Result := gaiError; + end; +end; + +function InterlockedExchangeTHandle(var VTarget: THandle; const AValue: THandle): THandle; +{$IFDEF USE_INLINE}inline;{$ENDIF} +begin + {$IFDEF HAS_TInterlocked} + {$IFDEF THANDLE_32} + Result := THandle(TInterlocked.Exchange(LongInt(VTarget), LongInt(AValue))); + {$ENDIF} + //Temporary workaround. TInterlocked for Emb really should accept 64 bit unsigned values as set of parameters + //for TInterlocked.Exchange since 64-bit wide integers are common on 64 bit platforms. + {$IFDEF THANDLE_64} + Result := THandle(TInterlocked.Exchange(Int64(VTarget), Int64(AValue))); + {$ENDIF} + {$ELSE} + {$IFDEF THANDLE_32} + Result := THandle(InterlockedExchange(LongInt(VTarget), LongInt(AValue))); + {$ENDIF} + {$IFDEF THANDLE_64} + Result := THandle(InterlockedExchange64(Int64(VTarget), Int64(AValue))); + {$ENDIF} + {$ENDIF} +end; + +procedure CloseLibrary; +var + h : THandle; +begin + {$IFNDEF WINCE} + {$IFNDEF WIN64} + //Only unload the IPv6 functions for Windows NT (2000 or greater). + //Note that Win64 was introduced after Windows XP. That was based on Windows + //Server code so we'll skip this in Win64. + //I'm just doing this as a minor shortcut. + if (Win32Platform <> VER_PLATFORM_WIN32_NT) or (Win32MajorVersion < 5) then begin + Exit; + end; + {$ENDIF} + {$ENDIF} + h := InterlockedExchangeTHandle(hWship6Dll, 0); + if h <> 0 then begin + FreeLibrary(h); + end; + {$IFNDEF WINCE} + h := InterlockedExchangeTHandle(hfwpuclntDll, 0); + if h <> 0 then begin + FreeLibrary(h); + end; + {$ENDIF} + GIdIPv6FuncsAvailable := False; + + getaddrinfo := nil; + getnameinfo := nil; + freeaddrinfo := nil; + {$IFNDEF WINCE} + WSASetSocketPeerTargetName := nil; + WSADeleteSocketPeerTargetName := nil; + WSAImpersonateSocketPeer := nil; + WSAQuerySocketSecurity := nil; + WSARevertImpersonation := nil; + {$ENDIF} +end; + +procedure InitLibrary; +begin + GIdIPv6FuncsAvailable := False; + {$IFNDEF WINCE} + {$IFNDEF WIN64} + //Only attempt to load the IPv6 functions for Windows NT (2000 or greater). + //Note that Win64 was introduced after Windows XP. That was based on Windows + //Server code so we'll skip this in Win64. + if (Win32Platform <> VER_PLATFORM_WIN32_NT) or (Win32MajorVersion < 5) then begin + Exit; + end; + {$ENDIF} + {$ENDIF} +{ +IMPORTANT!!! + +I am doing things this way because the functions we want are probably in +the Winsock2 dll. If they are not there, only then do you actually want +to try the Wship6.dll. I know it's a mess but I found that the functions +may not load if they aren't in Wship6.dll (and they aren't there in some +versions of Windows). + +hProcHandle provides a transparant way of managing the two possible library +locations. hWship6Dll is kept so we can unload the Wship6.dll if necessary. +} + //Winsock2 has to be loaded by IdWinsock first. + if not Net.Winsock2.Winsock2Loaded then + begin + Net.Winsock2.InitializeWinSock; + end; + hProcHandle := Net.Winsock2.WinsockHandle; + getaddrinfo := GetProcAddress(hProcHandle, fn_getaddrinfo); + if not Assigned(getaddrinfo) then + begin + hWship6Dll := SafeLoadLibrary(Wship6_dll); + hProcHandle := hWship6Dll; + getaddrinfo := GetProcAddress(hProcHandle, fn_getaddrinfo); {do not localize} + end; + + if Assigned(getaddrinfo) then + begin + getnameinfo := GetProcAddress(hProcHandle, fn_getnameinfo); {do not localize} + if Assigned(getnameinfo) then + begin + freeaddrinfo := GetProcAddress(hProcHandle, fn_freeaddrinfo); {do not localize} + if Assigned(freeaddrinfo) then + begin + GIdIPv6FuncsAvailable := True; + + //Additional functions should be initialized here. + {$IFNDEF WINCE} + inet_pton := GetProcAddress(hProcHandle, fn_inet_pton); {do not localize} + inet_ntop := GetProcAddress(hProcHandle, fn_inet_ntop); {do not localize} + GetAddrInfoEx := GetProcAddress(hProcHandle, fn_GetAddrInfoEx); {Do not localize} + SetAddrInfoEx := GetProcAddress(hProcHandle, fn_SetAddrInfoEx); {Do not localize} + FreeAddrInfoEx := GetProcAddress(hProcHandle, fn_FreeAddrInfoEx); {Do not localize} + hfwpuclntDll := SafeLoadLibrary(fwpuclnt_dll); + if hfwpuclntDll <> 0 then + begin + WSASetSocketPeerTargetName := GetProcAddress(hfwpuclntDll, 'WSASetSocketPeerTargetName'); {Do not localize} + WSADeleteSocketPeerTargetName := GetProcAddress(hfwpuclntDll, 'WSADeleteSocketPeerTargetName'); {Do not localize} + WSAImpersonateSocketPeer := GetProcAddress(hfwpuclntDll, 'WSAImpersonateSocketPeer'); {Do not localize} + WSAQuerySocketSecurity := GetProcAddress(hfwpuclntDll, 'WSAQuerySocketSecurity'); {Do not localize} + WSARevertImpersonation := GetProcAddress(hfwpuclntDll, 'WSARevertImpersonation'); {Do not localize} + end; + {$ENDIF} + Exit; + end; + end; + end; + + CloseLibrary; +end; + +initialization + InitLibrary; + +finalization + CloseLibrary; + +end. diff --git a/ThirdParty/DCS/README.en.md b/ThirdParty/DCS/README.en.md new file mode 100644 index 00000000..04b6e8bc --- /dev/null +++ b/ThirdParty/DCS/README.en.md @@ -0,0 +1,70 @@ +# Delphi Cross Platform Socket Communication Library + +Author: WiNDDRiVER(soulawing@gmail.com) + +### [中文](README.md) + +## Update list + +#### 2019.02.17 +- Fix the problem of memory leakage caused by TIoEventThread + > thank viniciusfbb for finding and fixing the problem +- Fix memory leak caused by [weak] + > when used with a third-party memory management library, there will be a memory leak. robertodellapasqua found the problem and pony5551 finally found the cause of the problem. Thank you very much! This should be a defect in Delphi's [weak] internal implementation. The problem was solved after replacing [weak] with [unsafe] + +#### 2019.01.15 +- increase mbedtls support + - mbedtls enabling method: turn on \_\_CROSS\_SSL\_\_ and \_\_MBED\_TLS\_\_ in the engineering compilation option, and add the directory under MbedObj to the Library path of the corresponding platform + - mbedtls support is not stable at present, please do not use it in production environment + + #### 2017.08.22 + - code refactoring, with many modifications, see source code for details + - Several new interface have been added. See demos for usage + - ICrossSocket + - ICrossSslSocket + - ICrossServer + - ICrossSslServer + +## Features +- Use different IO models for different platforms: + - IOCP + > Windows + + - KQUEUE + > FreeBSD(MacOSX, iOS...) + + - EPOLL + > Linux(Linux, Android...) + + - Supports extremely high concurrency + + - Windows + > can run more than 100000 concurrent number, need to modify the registry to adjust the default maximum port number + + - Mac + > preliminary tests were conducted. the test environment was OSX 10.9.5 in the virtual machine. even if the limit on the number of handles in the system was modified, + > can only open more than 32000 concurrent connections at most, perhaps OSX Server version can support higher concurrency + + - IPv4 and IPv6 are supported at the same time. + - Zero Memory Copy + +## Passed the test + - Windows + - OSX + - iOS + - Android + - Linux + +## Suggested Development Environment + - To give full play to cross-platform functions, please use Delphi 10.2 Tokyo and above + - The minimum requirement is to support the Delphi version of generic and anonymous functions. I am not sure from which version generic and anonymous functions are supported. + +## Known Issues + - SSL under non - Windows platform is unstable, please do not use it in production environment + +## Some Test Screenshots +- **HTTP**(ubuntu 16.04 desktop for server) +![20170607110011](https://user-images.githubusercontent.com/3221597/26860614-61b750b4-4b71-11e7-8afc-74c3ebf16f7e.png) + +- **HTTPS**(ubuntu 16.04 desktop for server) +![20170607142650](https://user-images.githubusercontent.com/3221597/26868229-d8d79f40-4b9a-11e7-927c-bfb3d7e6e55d.png) diff --git a/ThirdParty/DCS/README.md b/ThirdParty/DCS/README.md new file mode 100644 index 00000000..f523cbe0 --- /dev/null +++ b/ThirdParty/DCS/README.md @@ -0,0 +1,74 @@ +# Delphi 跨平台 Socket 通讯库 + +作者: WiNDDRiVER(soulawing@gmail.com) + +### [English](README.en.md) + +## 更新记录 + +#### 2019.02.17 +- 修复 TIoEventThread 可能引起的内存泄漏的问题 + > 感谢 viniciusfbb 发现并修复了该问题 +- 修复 [weak] 引起的内存泄漏问题 + > 与第三方内存管理库搭配使用时会出现内存泄漏,robertodellapasqua 发现了该问题,最终由 pony5551 找到了该问题产生的原因,特此感谢!这应该是 Delphi 的 [weak] 内部实现有缺陷,将 [weak] 替换成 [unsafe] 后该问题得以解决。 + +#### 2019.01.15 +- 增加 mbedtls 支持 + - mbedtls启用方法:在工程编译选项中开启 \_\_CROSS\_SSL\_\_ 和 \_\_MBED\_TLS\_\_ 这两个编译开关, 并且将 MbedObj 下的目录添加到对应平台的 Library path 中 + - 目前 mbedtls 支持还不够稳定, 请勿用于生产环境 + +#### 2017.08.22 +- 代码重构, 做了大量修改, 详见源码 +- 增加了几个新的 interface, 用法详见 demos + - ICrossSocket + - ICrossSslSocket + - ICrossServer + - ICrossSslServer + + +## 特性 + +- 针对不同平台使用不同的IO模型: + - IOCP + > Windows + + - KQUEUE + > FreeBSD(MacOSX, iOS...) + + - EPOLL + > Linux(Linux, Android...) + +- 支持极高的并发 + + - Windows + > 能跑10万以上的并发数, 需要修改注册表调整默认的最大端口数 + + - Mac + > 做了初步测试, 测试环境为虚拟机中的 OSX 10.9.5, 即便修改了系统的句柄数限制, + > 最多也只能打开32000多个并发连接, 或许 OSX Server 版能支持更高的并发吧 + +- 同时支持IPv4、IPv6 + +- 零内存拷贝 + +## 已通过测试 +- Windows +- OSX +- iOS +- Android +- Linux + +## 建议开发环境 +- 要发挥跨平台的完整功能请使用Delphi 10.2 Tokyo及以上的版本 +- 最低要求支持泛型和匿名函数的Delphi版本, 具体是从哪个版本开始支持泛型和匿名函数的我也不是太清楚 + +## 已知问题 +- 非Windows平台下的SSL不稳定, 请勿用于生产环境 + +## 部分测试截图 + +- **HTTP**(服务端为ubuntu 16.04 desktop) +![20170607110011](https://user-images.githubusercontent.com/3221597/26860614-61b750b4-4b71-11e7-8afc-74c3ebf16f7e.png) + +- **HTTPS**(服务端为ubuntu 16.04 desktop) +![20170607142650](https://user-images.githubusercontent.com/3221597/26868229-d8d79f40-4b9a-11e7-927c-bfb3d7e6e55d.png) diff --git a/ThirdParty/DCS/Utils/Utils.DateTime.pas b/ThirdParty/DCS/Utils/Utils.DateTime.pas new file mode 100644 index 00000000..ff3e11ce --- /dev/null +++ b/ThirdParty/DCS/Utils/Utils.DateTime.pas @@ -0,0 +1,565 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Utils.DateTime; + +interface + +uses + System.SysUtils, + System.DateUtils, + System.Types, + System.Math; + +type + TDateTimeHelper = record helper for TDateTime + private const + CMillisPerDay = Int64(MSecsPerSec * SecsPerMin * MinsPerHour * HoursPerDay); + private + function GetDay: Word; inline; + function GetDate: TDateTime; inline; + function GetDayOfWeek: Word; inline; + function GetDayOfYear: Word; inline; + function GetHour: Word; inline; + function GetMillisecond: Word; inline; + function GetMinute: Word; inline; + function GetMonth: Word; inline; + function GetSecond: Word; inline; + function GetTime: TDateTime; inline; + function GetYear: Integer; inline; + class function GetNow: TDateTime; static; inline; + class function GetToday: TDateTime; static; inline; + class function GetTomorrow: TDateTime; static; inline; + class function GetYesterDay: TDateTime; static; inline; + procedure SetYear(const Value: Integer); inline; + procedure SetDay(const Value: Word); inline; + procedure SetHour(const Value: Word); inline; + procedure SetMillisecond(const Value: Word); inline; + procedure SetMinute(const Value: Word); inline; + procedure SetMonth(const Value: Word); inline; + procedure SetSecond(const Value: Word); inline; + procedure SetDate(const Value: TDateTime); inline; + procedure SetTime(const Value: TDateTime); inline; + public + class function Create(const AYear, AMonth, ADay: Word): TDateTime; overload; static; inline; + class function Create(const AYear, AMonth, ADay, AHour, AMinute, ASecond, + AMillisecond: Word): TDateTime; overload; static; inline; + + class property Now: TDateTime read GetNow; + class property Today: TDateTime read GetToday; + class property Yesterday: TDateTime read GetYesterDay; + class property Tomorrow: TDateTime read GetTomorrow; + + property Date: TDateTime read GetDate write SetDate; + property Time: TDateTime read GetTime write SetTime; + + property DayOfWeek: Word read GetDayOfWeek; + property DayOfYear: Word read GetDayOfYear; + + property Year: Integer read GetYear write SetYear; + property Month: Word read GetMonth write SetMonth; + property Day: Word read GetDay write SetDay; + property Hour: Word read GetHour write SetHour; + property Minute: Word read GetMinute write SetMinute; + property Second: Word read GetSecond write SetSecond; + property Millisecond: Word read GetMillisecond write SetMillisecond; + + function ToString(const AFormatStr: string = ''): string; inline; + function ToMilliseconds: Int64; inline; + + function StartOfYear: TDateTime; inline; + function EndOfYear: TDateTime; inline; + function StartOfMonth: TDateTime; inline; + function EndOfMonth: TDateTime; inline; + function StartOfWeek: TDateTime; inline; + function EndOfWeek: TDateTime; inline; + function StartOfDay: TDateTime; inline; + function EndOfDay: TDateTime; inline; + + function AddYears(const ANumberOfYears: Integer = 1): TDateTime; inline; + function AddMonths(const ANumberOfMonths: Integer = 1): TDateTime; inline; + function AddDays(const ANumberOfDays: Integer = 1): TDateTime; inline; + function AddHours(const ANumberOfHours: Int64 = 1): TDateTime; inline; + function AddMinutes(const ANumberOfMinutes: Int64 = 1): TDateTime; inline; + function AddSeconds(const ANumberOfSeconds: Int64 = 1): TDateTime; inline; + function AddMilliseconds(const ANumberOfMilliseconds: Int64 = 1): TDateTime; inline; + + function CompareTo(const ADateTime: TDateTime): TValueRelationship; inline; + function Equals(const ADateTime: TDateTime): Boolean; inline; + function IsSameDay(const ADateTime: TDateTime): Boolean; inline; + function IsSameMonth(const ADateTime: TDateTime): Boolean; + function IsSameYear(const ADateTime: TDateTime): Boolean; + function InRange(const AStartDateTime, AEndDateTime: TDateTime; const AInclusive: Boolean = True): Boolean; inline; + function IsInLeapYear: Boolean; inline; + function IsToday: Boolean; inline; + function IsAM: Boolean; inline; + function IsPM: Boolean; inline; + + function YearsBetween(const ADateTime: TDateTime): Integer; inline; + function MonthsBetween(const ADateTime: TDateTime): Integer; inline; + function WeeksBetween(const ADateTime: TDateTime): Integer; inline; + function DaysBetween(const ADateTime: TDateTime): Integer; inline; + function HoursBetween(const ADateTime: TDateTime): Int64; inline; + function MinutesBetween(const ADateTime: TDateTime): Int64; inline; + function SecondsBetween(const ADateTime: TDateTime): Int64; inline; + function MilliSecondsBetween(const ADateTime: TDateTime): Int64; inline; + + function YearsDiffer(const ADateTime: TDateTime): Integer; inline; + function MonthsDiffer(const ADateTime: TDateTime): Integer; inline; + function WeeksDiffer(const ADateTime: TDateTime): Integer; inline; + function DaysDiffer(const ADateTime: TDateTime): Integer; inline; + function HoursDiffer(const ADateTime: TDateTime): Int64; inline; + function MinutesDiffer(const ADateTime: TDateTime): Int64; inline; + function SecondsDiffer(const ADateTime: TDateTime): Int64; inline; + function MilliSecondsDiffer(const ADateTime: TDateTime): Int64; inline; + + function WithinYears(const ADateTime: TDateTime; const AYears: Integer): Boolean; inline; + function WithinMonths(const ADateTime: TDateTime; const AMonths: Integer): Boolean; inline; + function WithinWeeks(const ADateTime: TDateTime; const AWeeks: Integer): Boolean; inline; + function WithinDays(const ADateTime: TDateTime; const ADays: Integer): Boolean; inline; + function WithinHours(const ADateTime: TDateTime; const AHours: Int64): Boolean; inline; + function WithinMinutes(const ADateTime: TDateTime; const AMinutes: Int64): Boolean; inline; + function WithinSeconds(const ADateTime: TDateTime; const ASeconds: Int64): Boolean; inline; + function WithinMilliseconds(const ADateTime: TDateTime; const AMilliseconds: Int64): Boolean; inline; + + function ToUniversalTime(const AForceDaylight: Boolean = False): TDateTime; inline; + function ToLocalTime: TDateTime; inline; + end; + +implementation + +{ TDateTimeHelper } + +function TDateTimeHelper.AddDays(const ANumberOfDays: Integer): TDateTime; +begin + Result := IncDay(Self, ANumberOfDays); +end; + +function TDateTimeHelper.AddHours(const ANumberOfHours: Int64): TDateTime; +begin + Result := IncHour(Self, ANumberOfHours); +end; + +function TDateTimeHelper.AddMilliseconds(const ANumberOfMilliseconds: Int64): TDateTime; +begin + Result := IncMilliSecond(Self, ANumberOfMilliseconds); +end; + +function TDateTimeHelper.AddMinutes(const ANumberOfMinutes: Int64): TDateTime; +begin + Result := IncMinute(Self, ANumberOfMinutes); +end; + +function TDateTimeHelper.AddMonths(const ANumberOfMonths: Integer): TDateTime; +begin + Result := IncMonth(Self, ANumberOfMonths); +end; + +function TDateTimeHelper.AddSeconds(const ANumberOfSeconds: Int64): TDateTime; +begin + Result := IncSecond(Self, ANumberOfSeconds); +end; + +function TDateTimeHelper.AddYears(const ANumberOfYears: Integer): TDateTime; +begin + Result := IncYear(Self, ANumberOfYears); +end; + +function TDateTimeHelper.CompareTo(const ADateTime: TDateTime): TValueRelationship; +begin + Result := CompareDateTime(Self, ADateTime); +end; + +class function TDateTimeHelper.Create(const AYear, AMonth, + ADay: Word): TDateTime; +begin + Result := EncodeDate(AYear, AMonth, ADay); +end; + +class function TDateTimeHelper.Create(const AYear, AMonth, ADay, AHour, AMinute, + ASecond, AMillisecond: Word): TDateTime; +begin + Result := EncodeDateTime(AYear, AMonth, ADay, AHour, AMinute, ASecond, AMillisecond); +end; + +function TDateTimeHelper.DaysBetween(const ADateTime: TDateTime): Integer; +begin + Result := System.DateUtils.DaysBetween(Self, ADateTime); +end; + +function TDateTimeHelper.DaysDiffer(const ADateTime: TDateTime): Integer; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) div CMillisPerDay; +end; + +function TDateTimeHelper.EndOfDay: TDateTime; +begin + Result := EndOfTheDay(Self); +end; + +function TDateTimeHelper.EndOfMonth: TDateTime; +begin + Result := EndOfTheMonth(Self); +end; + +function TDateTimeHelper.EndOfWeek: TDateTime; +begin + Result := EndOfTheWeek(Self); +end; + +function TDateTimeHelper.EndOfYear: TDateTime; +begin + Result := EndOfTheYear(Self); +end; + +function TDateTimeHelper.Equals(const ADateTime: TDateTime): Boolean; +begin + Result := SameDateTime(Self, ADateTime); +end; + +function TDateTimeHelper.GetDate: TDateTime; +begin + Result := DateOf(Self); +end; + +function TDateTimeHelper.GetDay: Word; +begin + Result := DayOf(Self); +end; + +function TDateTimeHelper.GetDayOfWeek: Word; +begin + Result := DayOfTheWeek(Self); +end; + +function TDateTimeHelper.GetDayOfYear: Word; +begin + Result := DayOfTheYear(Self); +end; + +function TDateTimeHelper.GetHour: Word; +begin + Result := HourOf(Self); +end; + +function TDateTimeHelper.GetMillisecond: Word; +begin + Result := MilliSecondOf(Self); +end; + +function TDateTimeHelper.GetMinute: Word; +begin + Result := MinuteOf(Self); +end; + +function TDateTimeHelper.GetMonth: Word; +begin + Result := MonthOf(Self); +end; + +class function TDateTimeHelper.GetNow: TDateTime; +begin + Result := System.SysUtils.Now; +end; + +function TDateTimeHelper.GetSecond: Word; +begin + Result := SecondOf(Self); +end; + +function TDateTimeHelper.GetTime: TDateTime; +begin + Result := TimeOf(Self); +end; + +class function TDateTimeHelper.GetToday: TDateTime; +begin + Result := System.SysUtils.Date; +end; + +class function TDateTimeHelper.GetTomorrow: TDateTime; +begin + Result := System.SysUtils.Date + 1; +end; + +function TDateTimeHelper.GetYear: Integer; +begin + Result := YearOf(Self); +end; + +class function TDateTimeHelper.GetYesterDay: TDateTime; +begin + Result := System.SysUtils.Date - 1; +end; + +function TDateTimeHelper.HoursBetween(const ADateTime: TDateTime): Int64; +begin + Result := System.DateUtils.HoursBetween(Self, ADateTime); +end; + +function TDateTimeHelper.HoursDiffer(const ADateTime: TDateTime): Int64; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) + div (MSecsPerSec * SecsPerMin * MinsPerHour); +end; + +function TDateTimeHelper.InRange(const AStartDateTime, AEndDateTime: TDateTime; const AInclusive: Boolean): Boolean; +begin + Result := DateTimeInRange(Self, AStartDateTime, AEndDateTime, AInclusive); +end; + +function TDateTimeHelper.IsAM: Boolean; +begin + Result := System.DateUtils.IsAM(Self); +end; + +function TDateTimeHelper.IsInLeapYear: Boolean; +begin + Result := System.DateUtils.IsInLeapYear(Self); +end; + +function TDateTimeHelper.IsPM: Boolean; +begin + Result := System.DateUtils.IsPM(Self); +end; + +function TDateTimeHelper.IsSameDay(const ADateTime: TDateTime): Boolean; +begin + Result := (Trunc(Self) = Trunc(ADateTime)); +end; + +function TDateTimeHelper.IsSameMonth(const ADateTime: TDateTime): Boolean; +var + Y, M1, M2, D: Word; +begin + DecodeDate(Self, Y, M1, D); + DecodeDate(ADateTime, Y, M2, D); + Result := (M1 = M2); +end; + +function TDateTimeHelper.IsSameYear(const ADateTime: TDateTime): Boolean; +var + Y1, Y2, M, D: Word; +begin + DecodeDate(Self, Y1, M, D); + DecodeDate(ADateTime, Y2, M, D); + Result := (Y1 = Y2); +end; + +function TDateTimeHelper.IsToday: Boolean; +begin + Result := System.DateUtils.IsToday(Self); +end; + +function TDateTimeHelper.MilliSecondsBetween(const ADateTime: TDateTime): Int64; +begin + Result := System.DateUtils.MilliSecondsBetween(Self, ADateTime); +end; + +function TDateTimeHelper.MilliSecondsDiffer(const ADateTime: TDateTime): Int64; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds); +end; + +function TDateTimeHelper.MinutesBetween(const ADateTime: TDateTime): Int64; +begin + Result := System.DateUtils.MinutesBetween(Self, ADateTime); +end; + +function TDateTimeHelper.MinutesDiffer(const ADateTime: TDateTime): Int64; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) + div (MSecsPerSec * SecsPerMin); +end; + +function TDateTimeHelper.MonthsBetween(const ADateTime: TDateTime): Integer; +begin + Result := System.DateUtils.MonthsBetween(Self, ADateTime); +end; + +function TDateTimeHelper.MonthsDiffer(const ADateTime: TDateTime): Integer; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) + div Round(CMillisPerDay * ApproxDaysPerMonth); +end; + +function TDateTimeHelper.SecondsBetween(const ADateTime: TDateTime): Int64; +begin + Result := System.DateUtils.SecondsBetween(Self, ADateTime); +end; + +function TDateTimeHelper.SecondsDiffer(const ADateTime: TDateTime): Int64; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) + div (MSecsPerSec); +end; + +procedure TDateTimeHelper.SetDate(const Value: TDateTime); +begin + Self := Trunc(Value) + Frac(Self); +end; + +procedure TDateTimeHelper.SetDay(const Value: Word); +begin + Self := RecodeDay(Self, Value); +end; + +procedure TDateTimeHelper.SetHour(const Value: Word); +begin + Self := RecodeHour(Self, Value); +end; + +procedure TDateTimeHelper.SetMillisecond(const Value: Word); +begin + Self := RecodeMilliSecond(Self, Value); +end; + +procedure TDateTimeHelper.SetMinute(const Value: Word); +begin + Self := RecodeMinute(Self, Value); +end; + +procedure TDateTimeHelper.SetMonth(const Value: Word); +begin + Self := RecodeMonth(Self, Value); +end; + +procedure TDateTimeHelper.SetSecond(const Value: Word); +begin + Self := RecodeSecond(Self, Value); +end; + +procedure TDateTimeHelper.SetTime(const Value: TDateTime); +begin + Self := Trunc(Self) + Frac(Value); +end; + +procedure TDateTimeHelper.SetYear(const Value: Integer); +begin + Self := RecodeYear(Self, Value); +end; + +function TDateTimeHelper.StartOfDay: TDateTime; +begin + Result := StartOfTheDay(Self); +end; + +function TDateTimeHelper.StartOfMonth: TDateTime; +begin + Result := StartOfTheMonth(Self); +end; + +function TDateTimeHelper.StartOfWeek: TDateTime; +begin + Result := StartOfTheWeek(Self); +end; + +function TDateTimeHelper.StartOfYear: TDateTime; +begin + Result := StartOfTheYear(Self); +end; + +function TDateTimeHelper.ToLocalTime: TDateTime; +begin + Result := TTimeZone.Local.ToLocalTime(Self); +end; + +function TDateTimeHelper.ToMilliseconds: Int64; +var + LTimeStamp: TTimeStamp; +begin + LTimeStamp := DateTimeToTimeStamp(Self); + Result := (Int64(LTimeStamp.Date) * MSecsPerDay) + LTimeStamp.Time; +end; + +function TDateTimeHelper.ToString(const AFormatStr: string): string; +begin + if AFormatStr = '' then + Result := DateToStr(Self) + else + Result := FormatDateTime(AFormatStr, Self); +end; + +function TDateTimeHelper.ToUniversalTime( + const AForceDaylight: Boolean): TDateTime; +begin + Result := TTimeZone.Local.ToUniversalTime(Self, AForceDaylight); +end; + +function TDateTimeHelper.WeeksBetween(const ADateTime: TDateTime): Integer; +begin + Result := System.DateUtils.WeeksBetween(Self, ADateTime); +end; + +function TDateTimeHelper.WeeksDiffer(const ADateTime: TDateTime): Integer; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) + div (CMillisPerDay * DaysPerWeek); +end; + +function TDateTimeHelper.WithinDays(const ADateTime: TDateTime; + const ADays: Integer): Boolean; +begin + Result := System.DateUtils.WithinPastDays(Self, ADateTime, ADays); +end; + +function TDateTimeHelper.WithinHours(const ADateTime: TDateTime; + const AHours: Int64): Boolean; +begin + Result := System.DateUtils.WithinPastHours(Self, ADateTime, AHours); +end; + +function TDateTimeHelper.WithinMilliseconds(const ADateTime: TDateTime; + const AMilliseconds: Int64): Boolean; +begin + Result := System.DateUtils.WithinPastMilliSeconds(Self, ADateTime, AMilliseconds); +end; + +function TDateTimeHelper.WithinMinutes(const ADateTime: TDateTime; + const AMinutes: Int64): Boolean; +begin + Result := System.DateUtils.WithinPastMinutes(Self, ADateTime, AMinutes); +end; + +function TDateTimeHelper.WithinMonths(const ADateTime: TDateTime; + const AMonths: Integer): Boolean; +begin + Result := System.DateUtils.WithinPastMonths(Self, ADateTime, AMonths); +end; + +function TDateTimeHelper.WithinSeconds(const ADateTime: TDateTime; + const ASeconds: Int64): Boolean; +begin + Result := System.DateUtils.WithinPastSeconds(Self, ADateTime, ASeconds); +end; + +function TDateTimeHelper.WithinWeeks(const ADateTime: TDateTime; + const AWeeks: Integer): Boolean; +begin + Result := System.DateUtils.WithinPastWeeks(Self, ADateTime, AWeeks); +end; + +function TDateTimeHelper.WithinYears(const ADateTime: TDateTime; + const AYears: Integer): Boolean; +begin + Result := System.DateUtils.WithinPastYears(Self, ADateTime, AYears); +end; + +function TDateTimeHelper.YearsBetween(const ADateTime: TDateTime): Integer; +begin + Result := System.DateUtils.YearsBetween(Self, ADateTime); +end; + +function TDateTimeHelper.YearsDiffer(const ADateTime: TDateTime): Integer; +begin + Result := (Self.ToMilliseconds - ADateTime.ToMilliseconds) + div Round(CMillisPerDay * ApproxDaysPerYear); +end; + +end. diff --git a/ThirdParty/DCS/Utils/Utils.Logger.pas b/ThirdParty/DCS/Utils/Utils.Logger.pas new file mode 100644 index 00000000..2d97b419 --- /dev/null +++ b/ThirdParty/DCS/Utils/Utils.Logger.pas @@ -0,0 +1,377 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Utils.Logger; + +interface + +uses + System.Classes, System.SysUtils, System.IOUtils, System.Diagnostics, + System.Generics.Collections, Utils.Utils, Utils.DateTime; + +type + TLogType = (ltNormal, ltWarning, ltError, ltException); + TLogTypeSets = set of TLogType; + +const + LogTypeStr: array [TLogType] of string = ('', 'WAR', 'ERR', 'EXP'); + +type + ILogger = interface + ['{D9AE7F7B-95DE-4840-98FA-A49A4368D658}'] + function GetFilters: TLogTypeSets; + procedure SetFilters(const Value: TLogTypeSets); + + function GetLogDir: string; + function GetLogFileName(ALogType: TLogType; ADate: TDateTime): string; + + procedure AppendLog(const ALog: string; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + procedure AppendLog(const ALog: string; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + procedure AppendLog(const Fmt: string; const Args: array of const; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + procedure AppendLog(const Fmt: string; const Args: array of const; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + + procedure Flush; + + property Filters: TLogTypeSets read GetFilters write SetFilters; + end; + + TLogItem = record + Time: TDateTime; + Text: string; + end; + + TLogBuffer = TList; + + TLogger = class(TInterfacedObject, ILogger) + private const + FLUSH_INTERVAL = 200; + private + FFilters: TLogTypeSets; + + class var FLogger: ILogger; + class constructor Create; + class destructor Destroy; + + function GetFilters: TLogTypeSets; + procedure SetFilters(const Value: TLogTypeSets); + private + FBuffer: array [TLogType] of TLogBuffer; + FBufferLock: array [TLogType] of TObject; + FShutdown, FQuit: Boolean; + + procedure _Lock(const ALogType: TLogType); inline; + procedure _Unlock(const ALogType: TLogType); inline; + procedure _WriteLogFile(const ALogType: TLogType); + procedure _WriteAllLogFiles; inline; + procedure _CreateWriteThread; + procedure _Shutdown; inline; + protected + procedure _AppendLogToBuffer(const S: string; ALogType: TLogType); + public + constructor Create; virtual; + destructor Destroy; override; + + function GetLogDir: string; + function GetLogFileName(ALogType: TLogType; ADate: TDateTime): string; + + procedure AppendLog(const ALog: string; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + procedure AppendLog(const ALog: string; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + procedure AppendLog(const Fmt: string; const Args: array of const; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + procedure AppendLog(const Fmt: string; const Args: array of const; ALogType: TLogType = ltNormal; const CRLF: string = ''); overload; + + procedure Flush; + + property Filters: TLogTypeSets read GetFilters write SetFilters; + + class property Logger: ILogger read FLogger; + end; + +procedure AppendLog(const ALog: string; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ';'); overload; +procedure AppendLog(const ALog: string; ALogType: TLogType = ltNormal; const CRLF: string = ';'); overload; +procedure AppendLog(const Fmt: string; const Args: array of const; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ';'); overload; +procedure AppendLog(const Fmt: string; const Args: array of const; ALogType: TLogType = ltNormal; const CRLF: string = ';'); overload; + +function Logger: ILogger; + +var + // Ĭ־Ŀ¼ + // ɳԶ趨 + DefaultLogDir: string = ''; + +implementation + +class constructor TLogger.Create; +begin + FLogger := TLogger.Create; +end; + +class destructor TLogger.Destroy; +begin +end; + +constructor TLogger.Create; +var + I: TLogType; +begin + FFilters := [ltNormal, ltWarning, ltError, ltException]; + + for I := Low(TLogType) to High(TLogType) do + begin + FBuffer[I] := TLogBuffer.Create; + FBufferLock[I] := TObject.Create; + end; + + _CreateWriteThread; +end; + +destructor TLogger.Destroy; +var + I: TLogType; +begin + Flush; + + _Shutdown; + + for I := Low(TLogType) to High(TLogType) do + begin + FreeAndNil(FBuffer[I]); + FreeAndNil(FBufferLock[I]); + end; + + inherited Destroy; +end; + +procedure TLogger.Flush; +begin + _WriteAllLogFiles; +end; + +function TLogger.GetFilters: TLogTypeSets; +begin + Result := FFilters; +end; + +function TLogger.GetLogDir: string; +begin + if (DefaultLogDir <> '') then + Result := DefaultLogDir + else + Result := + {$IFDEF MSWINDOWS} + TUtils.AppPath + + {$ELSE} + TUtils.AppDocuments + + {$ENDIF} + TUtils.AppName + '.log' + PathDelim; +end; + +function TLogger.GetLogFileName(ALogType: TLogType; ADate: TDateTime): string; +begin + Result := LogTypeStr[ALogType]; + if (Result <> '') then + Result := Result + '-'; + Result := Result + TUtils.DateTimeToStr(ADate, 'YYYY-MM-DD') + '.log'; +end; + +procedure TLogger.SetFilters(const Value: TLogTypeSets); +begin + FFilters := Value; +end; + +procedure TLogger._CreateWriteThread; +begin + TThread.CreateAnonymousThread( + procedure + var + LWatch: TStopwatch; + begin + LWatch := TStopwatch.StartNew; + while not FShutdown do + begin + if (LWatch.ElapsedTicks > FLUSH_INTERVAL) then + begin + Flush; + + LWatch.Reset; + LWatch.Start; + end; + Sleep(10); + end; + + Flush; + + FQuit := True; + end).Start; +end; + +procedure TLogger._Lock(const ALogType: TLogType); +begin + System.TMonitor.Enter(FBufferLock[ALogType]); +end; + +procedure TLogger._Shutdown; +begin + FShutdown := True; + while not FQuit do + Sleep(1); +end; + +procedure TLogger._Unlock(const ALogType: TLogType); +begin + System.TMonitor.Exit(FBufferLock[ALogType]); +end; + +procedure TLogger._WriteLogFile(const ALogType: TLogType); +var + LLogDir, LLogFile: string; + LLastTime: TDateTime; + I: Integer; + LLogItem: TLogItem; + LBuffer: TBytesStream; + + procedure _WriteLogToBuffer(const ALogItem: TLogItem); + var + LBytes: TBytes; + begin + LBytes := TEncoding.UTF8.GetBytes(ALogItem.Text); + LBuffer.Seek(0, TSeekOrigin.soEnd); + LBuffer.Write(LBytes, Length(LBytes)); + end; + + procedure _WriteBufferToFile(const ALogFile: string); + var + LStream: TFileStream; + LBytes: TBytes; + begin + try + LStream := TFile.Open(ALogFile, TFileMode.fmOpenOrCreate, TFileAccess.faReadWrite, TFileShare.fsRead); + try + LStream.Seek(0, TSeekOrigin.soEnd); + LBytes := LBuffer.Bytes; + SetLength(LBytes, LBuffer.Size); + LStream.Write(LBytes, Length(LBytes)); + finally + FreeAndNil(LStream); + end; + except + end; + end; +begin + _Lock(ALogType); + try + if (FBuffer[ALogType].Count <= 0) then Exit; + + LLastTime := 0; + LLogDir := GetLogDir; + ForceDirectories(LLogDir); + + LBuffer := TBytesStream.Create(nil); + try + for I := 0 to FBuffer[ALogType].Count - 1 do + begin + LLogItem := FBuffer[ALogType].Items[I]; + _WriteLogToBuffer(LLogItem); + + if not LLogItem.Time.IsSameDay(LLastTime) + or (I >= FBuffer[ALogType].Count - 1) then + begin + LLastTime := LLogItem.Time; + LLogFile := LLogDir + GetLogFileName(ALogType, LLogItem.Time); + _WriteBufferToFile(LLogFile); + LBuffer.Clear; + end; + end; + FBuffer[ALogType].Clear; + finally + FreeAndNil(LBuffer); + end; + finally + _Unlock(ALogType); + end; +end; + +procedure TLogger._WriteAllLogFiles; +var + I: TLogType; +begin + for I := Low(TLogType) to High(TLogType) do + _WriteLogFile(I); +end; + +procedure TLogger.AppendLog(const ALog: string; const ATimeFormat: string; ALogType: TLogType; const CRLF: string); +var + LText: string; +begin + if not (ALogType in FFilters) then Exit; + + if (CRLF <> '') then + LText := StringReplace(ALog, sLineBreak, CRLF, [rfReplaceAll]) + else + LText := ALog; + LText := TUtils.DateTimeToStr(Now, ATimeFormat) + ' ' + LText + sLineBreak; + + _AppendLogToBuffer(LText, ALogType); +end; + +procedure TLogger.AppendLog(const ALog: string; ALogType: TLogType; const CRLF: string); +begin + AppendLog(ALog, 'HH:NN:SS:ZZZ', ALogType, CRLF); +end; + +procedure TLogger.AppendLog(const Fmt: string; const Args: array of const; const ATimeFormat: string; ALogType: TLogType; const CRLF: string); +begin + AppendLog(TUtils.ThreadFormat(Fmt, Args), ATimeFormat, ALogType, CRLF); +end; + +procedure TLogger.AppendLog(const Fmt: string; const Args: array of const; ALogType: TLogType; const CRLF: string); +begin + AppendLog(TUtils.ThreadFormat(Fmt, Args), ALogType, CRLF); +end; + +procedure TLogger._AppendLogToBuffer(const S: string; ALogType: TLogType); +var + LLogItem: TLogItem; +begin + _Lock(ALogType); + try + LLogItem.Time := Now; + LLogItem.Text := S; + FBuffer[ALogType].Add(LLogItem); + finally + _Unlock(ALogType); + end; +end; + +procedure AppendLog(const ALog: string; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ';'); +begin + Logger.AppendLog(ALog, ATimeFormat, ALogType, CRLF); +end; + +procedure AppendLog(const ALog: string; ALogType: TLogType = ltNormal; const CRLF: string = ';'); +begin + Logger.AppendLog(ALog, ALogType, CRLF); +end; + +procedure AppendLog(const Fmt: string; const Args: array of const; const ATimeFormat: string; ALogType: TLogType = ltNormal; const CRLF: string = ';'); +begin + Logger.AppendLog(Fmt, Args, ATimeFormat, ALogType, CRLF); +end; + +procedure AppendLog(const Fmt: string; const Args: array of const; ALogType: TLogType = ltNormal; const CRLF: string = ';'); +begin + Logger.AppendLog(Fmt, Args, ALogType, CRLF); +end; + +function Logger: ILogger; +begin + Result := TLogger.FLogger; +end; + +end. + diff --git a/ThirdParty/DCS/Utils/Utils.RegEx.pas b/ThirdParty/DCS/Utils/Utils.RegEx.pas new file mode 100644 index 00000000..918a2301 --- /dev/null +++ b/ThirdParty/DCS/Utils/Utils.RegEx.pas @@ -0,0 +1,101 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Utils.RegEx; + +interface + +uses + System.RegularExpressions, System.RegularExpressionsCore; + +type + TMatchEvaluatorProc = reference to function(const AMatch: TMatch): string; + + IScopeEvaluator = interface + function GetMatchEvaluator: TMatchEvaluator; + + property MatchEvaluator: TMatchEvaluator read GetMatchEvaluator; + end; + + TScopeEvaluator = class(TInterfacedObject, IScopeEvaluator) + private + FMatchEvaluatorProc: TMatchEvaluatorProc; + + function MatchEvaluator(const AMatch: TMatch): string; + function GetMatchEvaluator: TMatchEvaluator; + public + constructor Create(AMatchEvaluatorProc: TMatchEvaluatorProc); + end; + + TRegExHelper = record helper for TRegEx + function Replace(const Input: string; Evaluator: TMatchEvaluatorProc): string; overload; + function Replace(const Input: string; Evaluator: TMatchEvaluatorProc; Count: Integer): string; overload; + class function Replace(const Input, Pattern: string; Evaluator: TMatchEvaluatorProc): string; overload; static; + class function Replace(const Input, Pattern: string; Evaluator: TMatchEvaluatorProc; Options: TRegExOptions): string; overload; static; + end; + +implementation + +{ TScopeEvaluator } + +constructor TScopeEvaluator.Create(AMatchEvaluatorProc: TMatchEvaluatorProc); +begin + FMatchEvaluatorProc := AMatchEvaluatorProc; +end; + +function TScopeEvaluator.GetMatchEvaluator: TMatchEvaluator; +begin + Result := Self.MatchEvaluator; +end; + +function TScopeEvaluator.MatchEvaluator(const AMatch: TMatch): string; +begin + if Assigned(FMatchEvaluatorProc) then + Result := FMatchEvaluatorProc(AMatch); +end; + +{ TRegExHelper } + +function TRegExHelper.Replace(const Input: string; + Evaluator: TMatchEvaluatorProc; Count: Integer): string; +var + LScopeEvaluator: IScopeEvaluator; +begin + LScopeEvaluator := TScopeEvaluator.Create(Evaluator); + Result := Self.Replace(Input, LScopeEvaluator.MatchEvaluator, Count); +end; + +function TRegExHelper.Replace(const Input: string; + Evaluator: TMatchEvaluatorProc): string; +var + LScopeEvaluator: IScopeEvaluator; +begin + LScopeEvaluator := TScopeEvaluator.Create(Evaluator); + Result := Self.Replace(Input, LScopeEvaluator.MatchEvaluator); +end; + +class function TRegExHelper.Replace(const Input, Pattern: string; + Evaluator: TMatchEvaluatorProc; Options: TRegExOptions): string; +var + LScopeEvaluator: IScopeEvaluator; +begin + LScopeEvaluator := TScopeEvaluator.Create(Evaluator); + Result := Replace(Input, Pattern, LScopeEvaluator.MatchEvaluator, Options); +end; + +class function TRegExHelper.Replace(const Input, Pattern: string; + Evaluator: TMatchEvaluatorProc): string; +var + LScopeEvaluator: IScopeEvaluator; +begin + LScopeEvaluator := TScopeEvaluator.Create(Evaluator); + Result := Replace(Input, Pattern, LScopeEvaluator.MatchEvaluator); +end; + +end. diff --git a/ThirdParty/DCS/Utils/Utils.Utils.pas b/ThirdParty/DCS/Utils/Utils.Utils.pas new file mode 100644 index 00000000..1cd395ba --- /dev/null +++ b/ThirdParty/DCS/Utils/Utils.Utils.pas @@ -0,0 +1,492 @@ +{******************************************************************************} +{ } +{ Delphi cross platform socket library } +{ } +{ Copyright (c) 2017 WiNDDRiVER(soulawing@gmail.com) } +{ } +{ Homepage: https://github.com/winddriver/Delphi-Cross-Socket } +{ } +{******************************************************************************} +unit Utils.Utils; + +interface + +uses + System.SysUtils, + System.Classes, + System.Types, + System.IOUtils, + System.Math, + System.Diagnostics, + System.TimeSpan, + System.Character, + System.SysConst; + +type + TUtils = class + private class var + FAppFile, FAppPath, FAppHome, FAppDocuments, FAppName: string; + private + class constructor Create; + public + class function CalcTickDiff(AStartTick, AEndTick: Cardinal): Cardinal; + class function TestTime(AProc: TProc): TTimeSpan; + class function StrToDateTime(const S, Fmt: string): TDateTime; overload; + class function StrToDateTime(const S: string): TDateTime; overload; + class function DateTimeToStr(const D: TDateTime; const Fmt: string): string; overload; + class function DateTimeToStr(const D: TDateTime): string; overload; + class function ThreadFormat(const Fmt: string; const Args: array of const): string; + class function BytesToStr(const BytesCount: Int64): string; static; + class function CompareVersion(const V1, V2: string): Integer; static; + class procedure DelayCall(ATick: Cardinal; AProc: TProc); static; + class function GetGUID: string; static; + class function RandomStr(const ABaseChars: string; ASize: Integer): string; static; + class function EditDistance(const ASourceStr, ATargetStr: string): Integer; static; + class function SimilarText(const AStr1, AStr2: string): Single; static; + + class function IsSpaceChar(const C: Char): Boolean; static; + class function UnicodeTrim(const S: string): string; static; + class function UnicodeTrimLeft(const S: string): string; static; + class function UnicodeTrimRight(const S: string): string; static; + class function StrIPos(const ASubStr, AStr: string; AOffset: Integer): Integer; static; + + class procedure BinToHex(ABuffer: Pointer; ABufSize: Integer; AText: PChar); overload; static; + class function BinToHex(ABuffer: Pointer; ABufSize: Integer): string; overload; static; inline; + class function BytesToHex(const ABytes: TBytes; AOffset, ACount: Integer): string; overload; static; inline; + class function BytesToHex(const ABytes: TBytes): string; overload; static; inline; + + class function GetFullFileName(const AFileName: string): string; static; + class function GetFileSize(const AFileName: string): Int64; static; + + // 判断两段日期是否有交集 + class function IsCrossDate(const AStartDate1, AEndDate1, AStartDate2, AEndDate2: TDateTime): Boolean; + + class property AppFile: string read FAppFile; + class property AppPath: string read FAppPath; + class property AppHome: string read FAppHome; + class property AppDocuments: string read FAppDocuments; // ios, android 可写 + class property AppName: string read FAppName; + end; + + TEncodingHelper = class helper for TEncoding + /// + /// 从内存块中直接解码出字符串, 省去先将内存块转换为TBytes, 从而提高效率 + /// + function GetString(ABytes: PByte; AByteCount: Integer): string; overload; + end; + +implementation + +{ TUtils } + +class constructor TUtils.Create; +begin + FAppFile := ParamStr(0); + FAppName := ChangeFileExt(ExtractFileName(FAppFile), ''); + FAppPath := IncludeTrailingPathDelimiter(ExtractFilePath(FAppFile)); + + {$IF defined(IOS) or defined(ANDROID)} + FAppHome := IncludeTrailingPathDelimiter(TPath.GetHomePath); + {$ELSE} + FAppHome := IncludeTrailingPathDelimiter(TPath.Combine(TPath.GetHomePath, FAppName)); + {$ENDIF} + + {$IF defined(IOS) or defined(ANDROID)} + FAppDocuments := IncludeTrailingPathDelimiter(TPath.GetDocumentsPath); + {$ELSE} + FAppDocuments := IncludeTrailingPathDelimiter(TPath.Combine(TPath.GetDocumentsPath, FAppName)); + {$ENDIF} +end; + +class function TUtils.DateTimeToStr(const D: TDateTime; + const Fmt: string): string; +begin + Result := FormatDateTime(Fmt, D, TFormatSettings.Create); +end; + +class function TUtils.DateTimeToStr(const D: TDateTime): string; +begin + Result := DateTimeToStr(D, 'yyyy-mm-dd hh:nn:ss'); +end; + +class procedure TUtils.DelayCall(ATick: Cardinal; AProc: TProc); +begin + TThread.CreateAnonymousThread( + procedure + begin + Sleep(ATick); + AProc(); + end).Start; +end; + +class function TUtils.GetFileSize(const AFileName: string): Int64; +var + LFileStream: TStream; +begin + LFileStream := TFile.Open(AFileName, TFileMode.fmOpen, TFileAccess.faRead, TFileShare.fsReadWrite); + try + Result := LFileStream.Size; + finally + FreeAndNil(LFileStream); + end; +end; + +class function TUtils.GetFullFileName(const AFileName: string): string; +begin + if + {$IFDEF MSWINDOWS} + // Windows 下不以驱动器号开头的文件名都视为相对路径 + not TPath.DriveExists(AFileName) + {$ELSE} + // Posix 下直接调用相对路径的现成函数判断 + TPath.IsRelativePath(AFileName) + {$ENDIF} + then + // 相对路径的文件名用程序所在路径补全 + Result := TPath.Combine(TUtils.AppPath, AFileName) + else + Result := AFileName; +end; + +class function TUtils.GetGUID: string; +var + LGuid: TGUID; +begin + CreateGUID(LGuid); + Result := Format('%.8x%.4x%.4x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x', + [LGuid.D1, LGuid.D2, LGuid.D3, LGuid.D4[0], LGuid.D4[1], LGuid.D4[2], LGuid.D4[3], + LGuid.D4[4], LGuid.D4[5], LGuid.D4[6], LGuid.D4[7]]); + +// SetLength(Result, 32); +// StrLFmt(PChar(Result), 32, '%.8x%.4x%.4x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x', +// [LGuid.D1, LGuid.D2, LGuid.D3, LGuid.D4[0], LGuid.D4[1], LGuid.D4[2], LGuid.D4[3], +// LGuid.D4[4], LGuid.D4[5], LGuid.D4[6], LGuid.D4[7]]); +end; + +class function TUtils.IsCrossDate(const AStartDate1, AEndDate1, AStartDate2, + AEndDate2: TDateTime): Boolean; +begin + Result := (AEndDate1 >= AStartDate2) and (AStartDate1 <= AEndDate2); +end; + +class function TUtils.IsSpaceChar(const C: Char): Boolean; +begin + Result := (C.GetUnicodeCategory in [ + TUnicodeCategory.ucControl, + TUnicodeCategory.ucUnassigned, + TUnicodeCategory.ucSpaceSeparator + ]); +end; + +class function TUtils.RandomStr(const ABaseChars: string; + ASize: Integer): string; +var + LBaseLow, LBaseHigh, I: Integer; +begin + Randomize; + LBaseLow := Low(ABaseChars); + LBaseHigh := High(ABaseChars); + SetLength(Result, ASize); + for I := Low(Result) to High(Result) do + Result[I] := ABaseChars[RandomRange(LBaseLow, LBaseHigh + 1)]; +end; + +class function TUtils.CalcTickDiff(AStartTick, AEndTick: Cardinal): Cardinal; +begin + if (AEndTick >= AStartTick) then + Result := AEndTick - AStartTick + else + Result := High(Cardinal) - AStartTick + AEndTick; +end; + +class function TUtils.CompareVersion(const V1, V2: string): Integer; +var + LArr1, LArr2: TArray; + I, I1, I2, LSize1, LSize2: Integer; +begin + LArr1 := V1.Split(['.']); + LArr2 := V2.Split(['.']); + LSize1 := Length(LArr1); + LSize2 := Length(LArr2); + + I := 0; + while (I < LSize1) and (I < LSize2) do + begin + I1 := StrToIntDef(LArr1[I], 0); + I2 := StrToIntDef(LArr2[I], 0); + if (I1 > I2) then + Exit(1) + else if (I1 < I2) then + Exit(-1); + + Inc(I); + end; + + Result := (LSize1 - LSize2); +end; + +class function TUtils.SimilarText(const AStr1, AStr2: string): Single; +begin + Result := 1 - (EditDistance(AStr1, AStr2) / Max(AStr1.Length, AStr2.Length)); +end; + +class function TUtils.StrIPos(const ASubStr, AStr: string; + AOffset: Integer): Integer; +var + I, LIterCnt, L, J: Integer; + PSubStr, PS: PChar; + LCh: Char; +begin + PSubStr := Pointer(ASubStr); + PS := Pointer(AStr); + if (PSubStr = nil) or (PS = nil) or (AOffset < 1) then + Exit(0); + L := Length(ASubStr); + { Calculate the number of possible iterations. } + LIterCnt := Length(AStr) - AOffset - L + 2; + if (L > 0) and (LIterCnt > 0) then + begin + Inc(PS, AOffset - 1); + I := 0; + LCh := UpCase(PSubStr[0]); + if L = 1 then // Special case when Substring length is 1 + repeat + if UpCase(PS[I]) = LCh then + Exit(I + AOffset); + Inc(I); + until I = LIterCnt + else + repeat + if UpCase(PS[I]) = LCh then + begin + J := 1; + repeat + if UpCase(PS[I + J]) = UpCase(PSubStr[J]) then + begin + Inc(J); + if J = L then + Exit(I + AOffset); + end + else + Break; + until False; + end; + Inc(I); + until I = LIterCnt; + end; + + Result := 0; +end; + +class function TUtils.StrToDateTime(const S: string): TDateTime; +begin + Result := StrToDateTime(S, 'yyyy-mm-dd hh:nn:ss'); +end; + +class function TUtils.StrToDateTime(const S, Fmt: string): TDateTime; +// Fmt格式字符串:空格前是日期格式,空格后是时间格式 +// 必须是这样:YYYY-MM-DD HH:NN:SS或者MM-DD-YYYY HH:NN:SS +// 不能用空格做时间单位中间的间隔符 + function GetSeparator(const S: string): Char; + begin + for Result in S do + if not CharInSet(Result, ['a'..'z', 'A'..'Z']) then Exit; + Result := #0; + end; +var + Fms: TFormatSettings; + DateFmt, TimeFmt: string; + p: Integer; +begin + p := Fmt.IndexOf(' '); + DateFmt := Fmt.Substring(0, p); + TimeFmt := Fmt.Substring(p + 1); + {$if COMPILERVERSION >= 20} + Fms := TFormatSettings.Create; + {$else} + GetLocaleFormatSettings(GetThreadLocale, Fms); + {$ifend} + Fms.DateSeparator := GetSeparator(DateFmt); + Fms.TimeSeparator := GetSeparator(TimeFmt); + Fms.ShortDateFormat := DateFmt; + Fms.LongDateFormat := DateFmt; + Fms.ShortTimeFormat := TimeFmt; + Fms.LongTimeFormat := TimeFmt; + Result := System.SysUtils.StrToDateTime(S, Fms); +end; + +class procedure TUtils.BinToHex(ABuffer: Pointer; ABufSize: Integer; + AText: PChar); +const + XD: array[0..15] of char = ('0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); +var + I: Integer; + PBuffer: PByte; + PText: PChar; +begin + PBuffer := ABuffer; + PText := AText; + for I := 0 to ABufSize - 1 do + begin + PText[0] := XD[(PBuffer[I] shr 4) and $0f]; + PText[1] := XD[PBuffer[I] and $0f]; + Inc(PText, 2); + end; +end; + +class function TUtils.BinToHex(ABuffer: Pointer; ABufSize: Integer): string; +begin + SetLength(Result, ABufSize * 2); + BinToHex(ABuffer, ABufSize, PChar(Result)); +end; + +class function TUtils.BytesToHex(const ABytes: TBytes; AOffset, + ACount: Integer): string; +begin + Result := BinToHex(@ABytes[AOffset], ACount); +end; + +class function TUtils.BytesToHex(const ABytes: TBytes): string; +begin + Result := BytesToHex(ABytes, 0, Length(ABytes)); +end; + +class function TUtils.BytesToStr(const BytesCount: Int64): string; +const + KBYTES = Int64(1024); + MBYTES = KBYTES * 1024; + GBYTES = MBYTES * 1024; + TBYTES = GBYTES * 1024; +begin + if (BytesCount = 0) then + Result := '' + else if (BytesCount < KBYTES) then + Result := Format('%dB', [BytesCount]) + else if (BytesCount < MBYTES) then + Result := FormatFloat('0.##KB', BytesCount / KBYTES) + else if (BytesCount < GBYTES) then + Result := FormatFloat('0.##MB', BytesCount / MBYTES) + else if (BytesCount < TBYTES) then + Result := FormatFloat('0.##GB', BytesCount / GBYTES) + else + Result := FormatFloat('0.##TB', BytesCount / TBYTES); +end; + +class function TUtils.TestTime(AProc: TProc): TTimeSpan; +var + LWatch: TStopwatch; +begin + LWatch := TStopwatch.StartNew; + AProc(); + LWatch.Stop; + Result := LWatch.Elapsed; +end; + +class function TUtils.ThreadFormat(const Fmt: string; + const Args: array of const): string; +begin + Result := Format(Fmt, Args, TFormatSettings.Create); +end; + +class function TUtils.UnicodeTrim(const S: string): string; +var + I, L: Integer; +begin + L := S.Length - 1; + I := 0; + if (L > -1) and not IsSpaceChar(S.Chars[I]) and not IsSpaceChar(S.Chars[L]) then Exit(S); + + while (I <= L) and IsSpaceChar(S.Chars[I]) do + Inc(I); + + if (I > L) then Exit(''); + + while IsSpaceChar(S.Chars[L]) do + Dec(L); + + Result := S.SubString(I, L - I + 1); +end; + +class function TUtils.UnicodeTrimLeft(const S: string): string; +var + I, L: Integer; +begin + L := S.Length - 1; + I := 0; + while (I <= L) and IsSpaceChar(S.Chars[I]) do + Inc(I); + if (I > 0) then + Result := S.SubString(I) + else + Result := S; +end; + +class function TUtils.UnicodeTrimRight(const S: string): string; +var + I: Integer; +begin + I := S.Length - 1; + if (I >= 0) and not IsSpaceChar(S.Chars[I]) then + Result := S + else + begin + while (I >= 0) and IsSpaceChar(S.Chars[I]) do + Dec(I); + Result := S.SubString(0, I + 1); + end; +end; + +class function TUtils.EditDistance(const ASourceStr, ATargetStr: string): Integer; +var + i, j, edIns, edDel, edRep: Integer; + d: TArray>; +begin + SetLength(d, Length(ASourceStr) + 1, Length(ATargetStr) + 1); + + for i := 0 to ASourceStr.Length do + d[i][0] := i; + + for j := 0 to ATargetStr.Length do + d[0][j] := j; + + for i := 1 to ASourceStr.Length do + begin + for j := 1 to ATargetStr.Length do + begin + if((ASourceStr[i - 1] = ATargetStr[j - 1])) then + begin + d[i][j] := d[i - 1][j - 1]; //不需要编辑操作 + end else + begin + edIns := d[i][j - 1] + 1; //ASourceStr 插入字符 + edDel := d[i - 1][j] + 1; //ASourceStr 删除字符 + edRep := d[i - 1][j - 1] + 1; //ASourceStr 替换字符 + + d[i][j] := Min(Min(edIns, edDel), edRep); + end; + end; + end; + + Result := d[ASourceStr.length][ATargetStr.length]; +end; + +{ TEncodingHelper } + +function TEncodingHelper.GetString(ABytes: PByte; AByteCount: Integer): string; +var + LSize: Integer; +begin + if (ABytes = nil) then + raise EEncodingError.CreateRes(@SInvalidSourceArray); + if (AByteCount < 0) then + raise EEncodingError.CreateResFmt(@SInvalidCharCount, [AByteCount]); + + LSize := GetCharCount(ABytes, AByteCount); + if (AByteCount > 0) and (LSize = 0) then + raise EEncodingError.CreateRes(@SNoMappingForUnicodeCharacter); + SetLength(Result, LSize); + GetChars(ABytes, AByteCount, PChar(Result), LSize); +end; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Cryptography.RSA.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Builder.pas similarity index 81% rename from ThirdParty/delphi-jose-jwt/Source/JOSE.Cryptography.RSA.pas rename to ThirdParty/delphi-jose-jwt/Source/JOSE.Builder.pas index 9544455d..d026d6ba 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Cryptography.RSA.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Builder.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -20,10 +20,28 @@ { } {******************************************************************************} -unit JOSE.Cryptography.RSA; +/// +/// Utility class to encode and decode a JWT +/// +unit JOSE.Builder; interface +uses + System.SysUtils, + JOSE.Types.Bytes, + JOSE.Core.Base, + JOSE.Core.Parts, + JOSE.Core.JWA, + JOSE.Core.JWK, + JOSE.Core.JWT, + JOSE.Core.JWS, + JOSE.Core.JWE; + implementation +uses + System.Types, + System.StrUtils; + end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.Validators.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.Validators.pas new file mode 100644 index 00000000..79c8dda2 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.Validators.pas @@ -0,0 +1,347 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// Utility class to encode and decode a JWT +/// +unit JOSE.Consumer.Validators; + +interface + +uses + System.SysUtils, System.Generics.Collections, + JOSE.Types.Bytes, + JOSE.Core.Base, + JOSE.Core.Parts, + JOSE.Core.JWT, + JOSE.Context; + +type + IJOSEValidator = interface + function Validate(AJOSEContext: TJOSEContext): string; + end; + + TJOSEDateClaimsParams = record + private + function GetEvaluationTime: TJOSENumericDate; + public + RequireExp: Boolean; + RequireIat: Boolean; + RequireNbf: Boolean; + StaticEvaluationTime: TDateTime; + AllowedClockSkewSeconds: Integer; + MaxFutureValidityInMinutes: Integer; + + class function New: TJOSEDateClaimsParams; static; + function SkewMessage: string; + property EvaluationTime: TJOSENumericDate read GetEvaluationTime; + end; + + TJOSEValidator = reference to function (AJOSEContext: TJOSEContext): string; + + TJOSEValidatorArray = TArray; + TJOSEValidatorArrayHelper = record helper for TJOSEValidatorArray + public + function Add(AValue: TJOSEValidator): Integer; + end; + + TJOSEClaimsValidators = class + class function DateClaimsValidator( + ADateParams: TJOSEDateClaimsParams): TJOSEValidator; + + class function audValidator(AAudience: TJOSEStringArray; + ARequired: Boolean = True): TJOSEValidator; + + class function issValidator(AIssuer: string; + ARequired: Boolean = True): TJOSEValidator; overload; + + class function issValidator(AIssuers: TJOSEStringArray; + ARequired: Boolean = True): TJOSEValidator; overload; + + class function subValidator(const ASubject: string; + ARequired: Boolean): TJOSEValidator; overload; + + class function subValidator( + ARequired: Boolean): TJOSEValidator; overload; + + class function jtiValidator(const AJwtId: string; + ARequired: Boolean): TJOSEValidator; overload; + + class function jtiValidator( + ARequired: Boolean): TJOSEValidator; overload; + end; + + + +implementation + +uses + System.DateUtils, + System.Types, + System.StrUtils; + +{ TJOSEDateClaimsParams } + +function TJOSEDateClaimsParams.GetEvaluationTime: TJOSENumericDate; +begin + if StaticEvaluationTime = 0 then + StaticEvaluationTime := Now; + + Result := TJOSENumericDate.Create(StaticEvaluationTime); +end; + +class function TJOSEDateClaimsParams.New: TJOSEDateClaimsParams; +begin + Result.RequireExp := False; + Result.RequireIat := False; + Result.RequireNbf := False; + Result.StaticEvaluationTime := 0; + Result.AllowedClockSkewSeconds := 0; + Result.MaxFutureValidityInMinutes := 0; +end; + +function TJOSEDateClaimsParams.SkewMessage: string; +begin + if AllowedClockSkewSeconds > 0 then + Result := Format( + '(even when providing [%d] seconds of leeway to account for clock skew)', + [AllowedClockSkewSeconds] + ) + else + Result := '.'; +end; + +{ TJOSEClaimsValidators } + +class function TJOSEClaimsValidators.audValidator(AAudience: TJOSEStringArray; + ARequired: Boolean = True): TJOSEValidator; +begin + Result := + function (AJOSEContext: TJOSEContext): string + var + LOk: Boolean; + LSingleAudience: string; + LClaims: TJWTClaims; + begin + Result := ''; + LClaims := AJOSEContext.GetClaims; + + if not LClaims.HasAudience then + if ARequired then + Exit('No Audience [aud] claim present') + else + Exit(''); + + LOk := False; + for LSingleAudience in LClaims.AudienceArray do + if AAudience.Contains(LSingleAudience) then + LOk := True; + + if not LOk then + begin + Result := 'Audience [aud] claim '; + + if AAudience.IsEmpty then + Result := Result + ' present in the JWT but no expected audience value(s) were provided to the JWT Consumer.' + else + Result := Result + ' doesn''t contain an acceptable identifier.'; + + Result := Result + ' Expected '; + + if Length(LClaims.AudienceArray) = 1 then + Result := Result + '[' + LClaims.Audience + ']' + else + Result := Result + 'one of [' + LClaims.Audience + ']'; + + Result := Result + ' as aud value.'; + end; + end +end; + +class function TJOSEClaimsValidators.DateClaimsValidator( + ADateParams: TJOSEDateClaimsParams): TJOSEValidator; +var + LDeltaInSeconds: Int64; +begin + Result := + function (AJOSEContext: TJOSEContext): string + var + LClaims: TJWTClaims; + LExpiration, LIssuedAt, LNotBefore: TJOSENumericDate; + begin + Result := ''; + LClaims := AJOSEContext.GetClaims; + + LExpiration := TJOSENumericDate.Create(LClaims.Expiration); + LIssuedAt := TJOSENumericDate.Create(LClaims.IssuedAt); + LNotBefore := TJOSENumericDate.Create(LClaims.NotBefore); + + if ADateParams.RequireExp and not LClaims.HasExpiration then + Exit('No Expiration Time [exp] claim present'); + + if ADateParams.RequireIat and not LClaims.HasIssuedAt then + Exit('No IssuedAt [iat] claim present'); + + if ADateParams.RequireNbf and not LClaims.HasNotBefore then + Exit('No NotBefore [nbf] claim present'); + + if LClaims.HasExpiration then + begin + if ADateParams.EvaluationTime.IsAfter(LExpiration, ADateParams.AllowedClockSkewSeconds) then + Exit(Format( + 'The JWT is no longer valid - the evaluation time [%d] is on or after the Expiration Time [exp=%d] claim value %s', + [ADateParams.EvaluationTime.AsSeconds, JSONDate(LClaims.Expiration), ADateParams.SkewMessage]) + ); + + if LClaims.HasIssuedAt and LExpiration.IsBefore(LIssuedAt, ADateParams.AllowedClockSkewSeconds) then + Exit(Format('The Expiration Time (exp=%d) claim value cannot be before the IssuedAt (iat=%d) claim value', + [LExpiration.AsSeconds, LIssuedAt.AsSeconds]) + ); + + if LClaims.HasNotBefore and LExpiration.IsBefore(LNotBefore, ADateParams.AllowedClockSkewSeconds) then + Exit(Format('The Expiration Time (exp=%d) claim value cannot be before the NotBefore (nbf=%d) claim value', + [LExpiration.AsSeconds, LNotBefore.AsSeconds]) + ); + + if ADateParams.MaxFutureValidityInMinutes > 0 then + begin + LDeltaInSeconds := + LExpiration.AsSeconds - + ADateParams.AllowedClockSkewSeconds - + ADateParams.EvaluationTime.AsSeconds; + + if LDeltaInSeconds > (ADateParams.MaxFutureValidityInMinutes * 60) then + Exit(Format('The Expiration Time [exp=%d] claim value cannot be more than [%d] minutes in the future relative to the evaluation time %[d] %s', + [LExpiration.AsSeconds, ADateParams.MaxFutureValidityInMinutes, ADateParams.EvaluationTime.AsSeconds, ADateParams.SkewMessage]) + ); + end; + end; + + if LClaims.HasNotBefore then + if (ADateParams.EvaluationTime.AsSeconds + ADateParams.AllowedClockSkewSeconds) < LNotBefore.AsSeconds then + Exit(Format('The JWT is not yet valid as the evaluation time [%d] is before the NotBefore [nbf=%d] claim time %s', + [ADateParams.EvaluationTime.AsSeconds, LNotBefore.AsSeconds, ADateParams.SkewMessage]) + ); + end; + ; +end; + +class function TJOSEClaimsValidators.issValidator(AIssuer: string; + ARequired: Boolean): TJOSEValidator; +var + LIssuers: TJOSEStringArray; +begin + LIssuers := TJOSEStringArray.Create; + if not AIssuer.IsEmpty then + begin + LIssuers.Push(AIssuer); + end; + Result := issValidator(LIssuers, ARequired); +end; + +class function TJOSEClaimsValidators.issValidator(AIssuers: TJOSEStringArray; + ARequired: Boolean): TJOSEValidator; +begin + Result := + function (AJOSEContext: TJOSEContext): string + var + LIssuer: string; + begin + Result := ''; + LIssuer := AJOSEContext.GetClaims.Issuer; + + if not AJOSEContext.GetClaims.HasIssuer and ARequired then + Exit(Format('No Issuer [iss] claim present but was expecting %s', + [AIssuers.ToStringPluralForm('one of ')])); + + if (AIssuers.Size > 0) and not AIssuers.Contains(LIssuer) then + Exit(Format('Issuer [iss] claim value [%s] doesn''t match expected value of [%s]', + [LIssuer, AIssuers.ToStringPluralForm('one of ')])); + end + ; +end; + +class function TJOSEClaimsValidators.jtiValidator(const AJwtId: string; + ARequired: Boolean): TJOSEValidator; +begin + Result := + function (AJOSEContext: TJOSEContext): string + var + LJWTId: string; + begin + Result := ''; + LJWTId := AJOSEContext.GetClaims.JWTId; + + if not AJOSEContext.GetClaims.HasJWTId and ARequired then + Exit('No JWT ID [jti] claim present.') + else + if not AJwtId.IsEmpty and not AJwtId.Equals(LJwtId) then + Exit(Format( + 'JWT Id [jti] claim value [%s] doesn''t match expected value of [%s]', + [LJwtId, AJwtId])); + end + ; +end; + +class function TJOSEClaimsValidators.jtiValidator(ARequired: Boolean): TJOSEValidator; +begin + Result := TJOSEClaimsValidators.jtiValidator('', ARequired); +end; + +class function TJOSEClaimsValidators.subValidator(const ASubject: string; + ARequired: Boolean): TJOSEValidator; +begin + Result := + function (AJOSEContext: TJOSEContext): string + var + LSubject: string; + begin + Result := ''; + LSubject := AJOSEContext.GetClaims.Subject; + + if not AJOSEContext.GetClaims.HasSubject and ARequired then + Exit('No Subject [sub] claim present') + else + if not ASubject.IsEmpty and not ASubject.Equals(LSubject) then + Exit(Format( + 'Subject [sub] claim value [%s] doesn''t match expected value of [%s]', + [LSubject, ASubject])); + end + ; +end; + +class function TJOSEClaimsValidators.subValidator(ARequired: Boolean): TJOSEValidator; +begin + Result := TJOSEClaimsValidators.subValidator('', ARequired); +end; + +{ TJOSEValidatorArrayHelper } + +function TJOSEValidatorArrayHelper.Add(AValue: TJOSEValidator): Integer; +begin + Result := Length(Self); + + SetLength(Self, Result + 1); + Self[Result] := AValue; +end; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.pas new file mode 100644 index 00000000..b28c37c3 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Consumer.pas @@ -0,0 +1,554 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// Utility class to encode and decode a JWT +/// +unit JOSE.Consumer; + +interface + +uses + System.SysUtils, System.Generics.Collections, System.Classes, + JOSE.Types.Bytes, + JOSE.Core.Base, + JOSE.Core.Parts, + JOSE.Core.JWA, + JOSE.Core.JWK, + JOSE.Core.JWT, + JOSE.Core.JWS, + JOSE.Core.JWE, + JOSE.Context, + JOSE.Consumer.Validators; + +type + EInvalidJWTException = class(Exception) + public + procedure SetDetails(ADetails: TStrings); + end; + + IJOSEConsumer = interface + ['{A270E909-6D79-4FC1-B4F6-B9911EAB8D36}'] + procedure Process(const ACompactToken: TJOSEBytes); + procedure ProcessContext(AContext: TJOSEContext); + procedure Validate(AContext: TJOSEContext); + end; + + TJOSEConsumer = class(TInterfacedObject, IJOSEConsumer) + private + FKey: TJOSEBytes; + FValidators: TJOSEValidatorArray; + FClaimsClass: TJWTClaimsClass; + + FRequireSignature: Boolean; + FRequireEncryption: Boolean; + FSkipSignatureVerification: Boolean; + FSkipVerificationKeyValidation: Boolean; + private + function SetKey(const AKey: TJOSEBytes): TJOSEConsumer; + function SetValidators(const AValidators: TJOSEValidatorArray): TJOSEConsumer; + function SetRequireSignature(ARequireSignature: Boolean): TJOSEConsumer; + function SetRequireEncryption(ARequireEncryption: Boolean): TJOSEConsumer; + function SetSkipSignatureVerification(ASkipSignatureVerification: Boolean): TJOSEConsumer; + function SetSkipVerificationKeyValidation(ASkipVerificationKeyValidation: Boolean): TJOSEConsumer; + public + constructor Create(AClaimsClass: TJWTClaimsClass); + + procedure Process(const ACompactToken: TJOSEBytes); + procedure ProcessContext(AContext: TJOSEContext); + procedure Validate(AContext: TJOSEContext); + end; + + + IJOSEConsumerBuilder = interface + ['{8EC9CB4B-3233-493B-9366-F06CFFA81D99}'] + // Custom Claim Class + function SetClaimsClass(AClaimsClass: TJWTClaimsClass): IJOSEConsumerBuilder; + // Key, Signature & Encryption verification + function SetEnableRequireEncryption: IJOSEConsumerBuilder; + function SetDisableRequireSignature: IJOSEConsumerBuilder; + function SetSkipSignatureVerification: IJOSEConsumerBuilder; + function SetSkipVerificationKeyValidation: IJOSEConsumerBuilder; + function SetVerificationKey(const AKey: TJOSEBytes): IJOSEConsumerBuilder; + function SetDecryptionKey(const AKey: TJOSEBytes): IJOSEConsumerBuilder; + // String-based claims + function SetSkipDefaultAudienceValidation(): IJOSEConsumerBuilder; + function SetExpectedAudience(ARequireAudience: Boolean; const AExpectedAudience: TArray): IJOSEConsumerBuilder; + function SetExpectedIssuer(ARequireIssuer: Boolean; const AExpectedIssuer: string): IJOSEConsumerBuilder; + function SetExpectedIssuers(ARequireIssuer: Boolean; const AExpectedIssuers: TArray): IJOSEConsumerBuilder; + function SetRequireSubject: IJOSEConsumerBuilder; + function SetExpectedSubject(const ASubject: string): IJOSEConsumerBuilder; + function SetRequireJwtId: IJOSEConsumerBuilder; + function SetExpectedJwtId(const AJwtId: string): IJOSEConsumerBuilder; + // Date-based Claims + function SetRequireExpirationTime: IJOSEConsumerBuilder; + function SetRequireIssuedAt: IJOSEConsumerBuilder; + function SetRequireNotBefore: IJOSEConsumerBuilder; + function SetEvaluationTime(AEvaluationTime: TDateTime): IJOSEConsumerBuilder; + function SetAllowedClockSkew(AClockSkew: Integer; ATimeUnit: TJOSETimeUnit): IJOSEConsumerBuilder; + function SetMaxFutureValidity(AMaxFutureValidity: Integer; ATimeUnit: TJOSETimeUnit): IJOSEConsumerBuilder; + // External validators + function RegisterValidator(AValidator: TJOSEValidator): IJOSEConsumerBuilder; + // Validators skipping + function SetSkipAllValidators: IJOSEConsumerBuilder; + function SetSkipAllDefaultValidators: IJOSEConsumerBuilder; + + function Build: TJOSEConsumer; + end; + + /// + /// Used to create the appropriate TJOSEConsumer for the JWT processing needs + /// + TJOSEConsumerBuilder = class(TInterfacedObject, IJOSEConsumerBuilder) + private + FValidators: TJOSEValidatorArray; + FDateValidatorParams: TJOSEDateClaimsParams; + FDateValidator: TJOSEValidator; + FAudValidator: TJOSEValidator; + FIssValidator: TJOSEValidator; + + FKey: TJOSEBytes; + FClaimsClass: TJWTClaimsClass; + FDisableRequireSignature: Boolean; + FEnableRequireEncryption: Boolean; + FSkipVerificationKeyValidation: Boolean; + FRequireJwtId: Boolean; + FRequireSubject: Boolean; + FSkipAllDefaultValidators: Boolean; + FSkipAllValidators: Boolean; + FSkipDefaultAudienceValidation: Boolean; + FSkipSignatureVerification: Boolean; + FSubject: string; + FJwtId: string; + constructor Create; + procedure BuildValidators(const ADateParams: TJOSEDateClaimsParams); + public + class function NewConsumer: IJOSEConsumerBuilder; + destructor Destroy; override; + + // Custom Claim Class + function SetClaimsClass(AClaimsClass: TJWTClaimsClass): IJOSEConsumerBuilder; + // Key, Signature & Encryption verification + function SetEnableRequireEncryption: IJOSEConsumerBuilder; + function SetDisableRequireSignature: IJOSEConsumerBuilder; + function SetSkipSignatureVerification: IJOSEConsumerBuilder; + function SetSkipVerificationKeyValidation: IJOSEConsumerBuilder; + function SetVerificationKey(const AKey: TJOSEBytes): IJOSEConsumerBuilder; + function SetDecryptionKey(const AKey: TJOSEBytes): IJOSEConsumerBuilder; + // String-based claims + function SetSkipDefaultAudienceValidation(): IJOSEConsumerBuilder; + function SetExpectedAudience(ARequireAudience: Boolean; const AExpectedAudience: TArray): IJOSEConsumerBuilder; + function SetExpectedIssuer(ARequireIssuer: Boolean; const AExpectedIssuer: string): IJOSEConsumerBuilder; + function SetExpectedIssuers(ARequireIssuer: Boolean; const AExpectedIssuers: TArray): IJOSEConsumerBuilder; + function SetRequireSubject: IJOSEConsumerBuilder; + function SetExpectedSubject(const ASubject: string): IJOSEConsumerBuilder; + function SetRequireJwtId: IJOSEConsumerBuilder; + function SetExpectedJwtId(const AJwtId: string): IJOSEConsumerBuilder; + // Date-based Claims + function SetRequireExpirationTime: IJOSEConsumerBuilder; + function SetRequireIssuedAt: IJOSEConsumerBuilder; + function SetRequireNotBefore: IJOSEConsumerBuilder; + function SetEvaluationTime(AEvaluationTime: TDateTime): IJOSEConsumerBuilder; + function SetAllowedClockSkew(AClockSkew: Integer; ATimeUnit: TJOSETimeUnit): IJOSEConsumerBuilder; + function SetMaxFutureValidity(AMaxFutureValidity: Integer; ATimeUnit: TJOSETimeUnit): IJOSEConsumerBuilder; + // External validators + function RegisterValidator(AValidator: TJOSEValidator): IJOSEConsumerBuilder; + // Validators skipping + function SetSkipAllValidators: IJOSEConsumerBuilder; + function SetSkipAllDefaultValidators: IJOSEConsumerBuilder; + + function Build: TJOSEConsumer; + end; + +implementation + +uses + System.Types, + System.StrUtils, + JOSE.Types.JSON; + +function TJOSEConsumerBuilder.Build: TJOSEConsumer; +begin + if not Assigned(FClaimsClass) then + FClaimsClass := TJWTClaims; + + Result := TJOSEConsumer.Create(FClaimsClass) + .SetKey(FKey) + .SetRequireSignature(not FDisableRequireSignature) + .SetRequireEncryption(FEnableRequireEncryption) + .SetSkipSignatureVerification(FSkipSignatureVerification) + .SetSkipVerificationKeyValidation(FSkipVerificationKeyValidation); + + BuildValidators(FDateValidatorParams); + Result.SetValidators(FValidators); +end; + +procedure TJOSEConsumerBuilder.BuildValidators(const ADateParams: TJOSEDateClaimsParams); +begin + if not FSkipAllValidators then + begin + if not FSkipAllDefaultValidators then + begin + if not FSkipDefaultAudienceValidation then + begin + if not Assigned(FAudValidator) then + FAudValidator := TJOSEClaimsValidators.audValidator(TJOSEStringArray.Create, False); + FValidators.add(FAudValidator); + end; + + if not Assigned(FIssValidator) then + FIssValidator := TJOSEClaimsValidators.issValidator('', False); + FValidators.Add(FIssValidator); + + if not Assigned(FDateValidator) then + FDateValidator := TJOSEClaimsValidators.DateClaimsValidator(ADateParams); + FValidators.Add(FDateValidator); + + if FSubject.IsEmpty then + FValidators.Add(TJOSEClaimsValidators.subValidator(FRequireSubject)) + else + FValidators.Add(TJOSEClaimsValidators.subValidator(FSubject, FRequireSubject)); + + if FJwtId.IsEmpty then + FValidators.Add(TJOSEClaimsValidators.jtiValidator(FRequireJwtId)) + else + FValidators.Add(TJOSEClaimsValidators.jtiValidator(FJwtId, FRequireJwtId)); + end; + end; +end; + +constructor TJOSEConsumerBuilder.Create; +begin + inherited; + FDateValidatorParams := TJOSEDateClaimsParams.New; +end; + +destructor TJOSEConsumerBuilder.Destroy; +begin + inherited; +end; + +class function TJOSEConsumerBuilder.NewConsumer: IJOSEConsumerBuilder; +begin + Result := Self.Create; +end; + +function TJOSEConsumerBuilder.RegisterValidator(AValidator: TJOSEValidator): IJOSEConsumerBuilder; +var + LLen: Integer; +begin + LLen := Length(FValidators); + SetLength(FValidators, LLen + 1); + FValidators[LLen] := AValidator; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetAllowedClockSkew(AClockSkew: Integer; + ATimeUnit: TJOSETimeUnit): IJOSEConsumerBuilder; +begin + FDateValidatorParams.AllowedClockSkewSeconds := ATimeUnit.ToSeconds(AClockSkew); + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetClaimsClass(AClaimsClass: TJWTClaimsClass): IJOSEConsumerBuilder; +begin + FClaimsClass := AClaimsClass; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetDecryptionKey(const AKey: TJOSEBytes): IJOSEConsumerBuilder; +begin + FKey := AKey; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetDisableRequireSignature: IJOSEConsumerBuilder; +begin + FDisableRequireSignature := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetEnableRequireEncryption: IJOSEConsumerBuilder; +begin + FEnableRequireEncryption := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetEvaluationTime(AEvaluationTime: TDateTime): IJOSEConsumerBuilder; +begin + FDateValidatorParams.StaticEvaluationTime := AEvaluationTime; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetExpectedAudience(ARequireAudience: Boolean; + const AExpectedAudience: TArray): IJOSEConsumerBuilder; +begin + FAudValidator := TJOSEClaimsValidators.audValidator(AExpectedAudience, ARequireAudience); + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetExpectedIssuer(ARequireIssuer: Boolean; + const AExpectedIssuer: string): IJOSEConsumerBuilder; +begin + FIssValidator := TJOSEClaimsValidators.issValidator(AExpectedIssuer, ARequireIssuer); + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetExpectedIssuers(ARequireIssuer: Boolean; + const AExpectedIssuers: TArray): IJOSEConsumerBuilder; +begin + FIssValidator := TJOSEClaimsValidators.issValidator(AExpectedIssuers, ARequireIssuer); + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetExpectedJwtId(const AJwtId: string): IJOSEConsumerBuilder; +begin + FJwtId := AJwtId; + SetRequireJwtId; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetExpectedSubject(const ASubject: string): IJOSEConsumerBuilder; +begin + FSubject := ASubject; + SetRequireSubject; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetMaxFutureValidity(AMaxFutureValidity: Integer; + ATimeUnit: TJOSETimeUnit): IJOSEConsumerBuilder; +begin + FDateValidatorParams.MaxFutureValidityInMinutes := ATimeUnit.ToMinutes(AMaxFutureValidity); + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetSkipVerificationKeyValidation: IJOSEConsumerBuilder; +begin + FSkipVerificationKeyValidation := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetRequireExpirationTime: IJOSEConsumerBuilder; +begin + FDateValidatorParams.RequireExp := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetRequireIssuedAt: IJOSEConsumerBuilder; +begin + FDateValidatorParams.RequireIat := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetRequireJwtId: IJOSEConsumerBuilder; +begin + FRequireJwtId := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetRequireNotBefore: IJOSEConsumerBuilder; +begin + FDateValidatorParams.RequireNbf := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetRequireSubject: IJOSEConsumerBuilder; +begin + FRequireSubject := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetSkipAllDefaultValidators: IJOSEConsumerBuilder; +begin + FSkipAllDefaultValidators := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetSkipAllValidators: IJOSEConsumerBuilder; +begin + FSkipAllValidators := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetSkipDefaultAudienceValidation: IJOSEConsumerBuilder; +begin + FSkipDefaultAudienceValidation := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetSkipSignatureVerification: IJOSEConsumerBuilder; +begin + FSkipSignatureVerification := True; + Result := Self as IJOSEConsumerBuilder; +end; + +function TJOSEConsumerBuilder.SetVerificationKey(const AKey: TJOSEBytes): IJOSEConsumerBuilder; +begin + FKey := AKey; + Result := Self as IJOSEConsumerBuilder; +end; + +{ TJOSEConsumer } + +constructor TJOSEConsumer.Create(AClaimsClass: TJWTClaimsClass); +begin + FClaimsClass := AClaimsClass; + FRequireSignature := True; +end; + +procedure TJOSEConsumer.Process(const ACompactToken: TJOSEBytes); +var + LContext: TJOSEContext; +begin + LContext := TJOSEContext.Create(ACompactToken, FClaimsClass); + try + ProcessContext(LContext); + finally + LContext.Free; + end; +end; + +procedure TJOSEConsumer.ProcessContext(AContext: TJOSEContext); +var + LJWS: TJWS; + LHasSignature: Boolean; + //LJWE: TJWE; +begin + LHasSignature := False; + if AContext.GetJOSEObject is TJWS then + begin + LJWS := AContext.GetJOSEObject; + + // JWS Signature Verification + if not FSkipSignatureVerification then + begin + if FSkipVerificationKeyValidation then + LJWS.SkipKeyValidation := True; + + LJWS.SetKey(Self.FKey); + if not LJWS.VerifySignature then + raise EJOSEException.Create('JWS signature is invalid: ' + LJWS.Signature); + end; + + if LJWS.HeaderAlgorithm <> TJOSEAlgorithmId.None.AsString then + LHasSignature := True; + + if FRequireSignature and not LHasSignature then + raise EJOSEException.Create('The JWT has no signature but the JWT Consumer is configured to require one'); + + end + else if AContext.GetJOSEObject is TJWE then + begin + + end; + + Validate(AContext); +end; + +function TJOSEConsumer.SetKey(const AKey: TJOSEBytes): TJOSEConsumer; +begin + FKey := AKey; + Result := Self; +end; + +function TJOSEConsumer.SetSkipVerificationKeyValidation(ASkipVerificationKeyValidation: Boolean): TJOSEConsumer; +begin + FSkipVerificationKeyValidation := ASkipVerificationKeyValidation; + Result := Self; +end; + +function TJOSEConsumer.SetRequireEncryption(ARequireEncryption: Boolean): TJOSEConsumer; +begin + FRequireEncryption := ARequireEncryption; + Result := Self; +end; + +function TJOSEConsumer.SetRequireSignature(ARequireSignature: Boolean): TJOSEConsumer; +begin + FRequireSignature := ARequireSignature; + Result := Self; +end; + +function TJOSEConsumer.SetSkipSignatureVerification(ASkipSignatureVerification: Boolean): TJOSEConsumer; +begin + FSkipSignatureVerification := ASkipSignatureVerification; + Result := Self; +end; + +function TJOSEConsumer.SetValidators(const AValidators: TJOSEValidatorArray): TJOSEConsumer; +begin + FValidators := AValidators; + Result := Self; +end; + +procedure TJOSEConsumer.Validate(AContext: TJOSEContext); +var + LValidator: TJOSEValidator; + LResult: string; + LIssues: TStringList; + LException: EInvalidJWTException; +begin + LIssues := TStringList.Create; + try + for LValidator in FValidators do + begin + LResult := LValidator(AContext); + if LResult.IsEmpty then + Continue; + LIssues.Add(LResult); + end; + if LIssues.Count > 0 then + begin + LException := EInvalidJWTException.CreateFmt( + 'JWT (claims: %s) rejected due to invalid claims.', + [TJSONUtils.ToJSON(AContext.GetClaims.JSON)]); + LException.SetDetails(LIssues); + + raise LException; + end; + finally + LIssues.Free; + end; +end; + +{ EInvalidJWTException } + +procedure EInvalidJWTException.SetDetails(ADetails: TStrings); +var + LDetails: TSTringList; +begin + if ADetails.Count > 0 then + begin + LDetails := TStringList.Create; + try + LDetails.Add(Message); + LDetails.Add('Validation errors:'); + LDetails.AddStrings(ADetails); + Message := LDetails.Text; + finally + LDetails.Free; + end; + end; +end; + +end. + diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Context.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Context.pas new file mode 100644 index 00000000..bc9ecf85 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Context.pas @@ -0,0 +1,139 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// Utility class to encode and decode a JWT +/// +unit JOSE.Context; + +interface + +uses + System.SysUtils, System.Generics.Collections, + JOSE.Types.Bytes, + JOSE.Core.Base, + JOSE.Core.Parts, + JOSE.Core.JWA, + JOSE.Core.JWK, + JOSE.Core.JWT, + JOSE.Core.JWS, + JOSE.Core.JWE; + +type + TJOSEContext = class + private + FCompactToken: TJOSEBytes; + FClaimsClass: TJWTClaimsClass; + FJOSEObject: TJOSEParts; + FJWT: TJWT; + procedure FromCompactToken; + public + constructor Create(const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass); + destructor Destroy; override; + + function GetJOSEObject: TJOSEParts; overload; + function GetJOSEObject: T; overload; + + function GetHeader: TJWTHeader; + + function GetClaims: TJWTClaims; overload; + function GetClaims: T; overload; + end; + +implementation + +uses + System.Types, + System.StrUtils; + +{ TJOSEContext } + +constructor TJOSEContext.Create(const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass); +begin + FCompactToken := ACompactToken; + FClaimsClass := AClaimsClass; + FJWT := TJWT.Create(FClaimsClass); + try + FromCompactToken; + except + FreeAndNil(FJWT); + end; +end; + +destructor TJOSEContext.Destroy; +begin + FJWT.Free; + FJOSEObject.Free; + inherited; +end; + +procedure TJOSEContext.FromCompactToken; +var + LRes: TStringDynArray; +begin + LRes := SplitString(FCompactToken, PART_SEPARATOR); + + case Length(LRes) of + 3: + begin + FJOSEObject := TJWS.Create(FJWT); + try + FJOSEObject.CompactToken := FCompactToken; + except + FreeAndNil(FJOSEObject); + end; + end; + 5: + begin + raise EJOSEException.Create('Compact Serialization appears to be a JWE Token wich is not (yet) supported'); + end; + else + raise EJOSEException.Create('Malformed Compact Serialization'); + end; +end; + +function TJOSEContext.GetClaims: TJWTClaims; +begin + Result := FJWT.Claims; +end; + +function TJOSEContext.GetClaims: T; +begin + Result := FJWT.Claims as T; +end; + +function TJOSEContext.GetHeader: TJWTHeader; +begin + Result := FJWT.Header; +end; + +function TJOSEContext.GetJOSEObject: TJOSEParts; +begin + Result := FJOSEObject; +end; + +function TJOSEContext.GetJOSEObject: T; +begin + Result := FJOSEObject as T; +end; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Base.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Base.pas index 6dcca7bf..d370a03d 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Base.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Base.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -25,17 +25,16 @@ /// unit JOSE.Core.Base; -{$I MARS.inc} - interface +{$SCOPEDENUMS ON} + uses - SysUtils, - Generics.Collections, - JOSE.Types.Bytes - // JOSE.Types.JSON; - , MARS.Core.JSON - ; + System.SysUtils, + System.Generics.Collections, + JOSE.Types.Arrays, + JOSE.Types.Bytes, + JOSE.Types.JSON; const PART_SEPARATOR: Char = '.'; @@ -43,35 +42,110 @@ interface type EJOSEException = class(Exception); + TJOSEStringArray = TJOSEArray; + + TJOSETimeUnit = (Days, Hours, Minutes, Seconds, Milliseconds); + TJOSETimeUnitHelper = record helper for TJOSETimeUnit + private + function Convert(ADuration: Cardinal; ADestUnit: TJOSETimeUnit): Cardinal; + public + function ToDays(ADuration: Cardinal): Cardinal; + function ToHours(ADuration: Cardinal): Cardinal; + function ToMinutes(ADuration: Cardinal): Cardinal; + function ToSeconds(ADuration: Cardinal): Cardinal; + function ToMilliseconds(ADuration: Cardinal): Cardinal; + end; + + TJOSENumericDate = record + private + const CONVERSION: Int64 = 1000; + private + FValue: TDateTime; + function GetAsMilliSeconds: Int64; + function GetAsSeconds: Int64; + procedure SetAsSeconds(const AValue: Int64); + public + constructor Create(AValue: TDateTime); + class function FromSeconds(ASecondsFromEpoch: Int64): TJOSENumericDate; static; + class function FromMilliseconds(AMillisecondsFromEpoch: Int64): TJOSENumericDate; static; + + procedure AddSeconds(ASeconds: Int64); + function IsBefore(const AWhen: TJOSENumericDate; ASkewSeconds: Integer): Boolean; + function IsOnOrAfter(const AWhen: TJOSENumericDate; ASkewSeconds: Integer): Boolean; + function IsAfter(const AWhen: TJOSENumericDate; ASkewSeconds: Integer): Boolean; + + property AsSeconds: Int64 read GetAsSeconds write SetAsSeconds; + property AsMilliSeconds: Int64 read GetAsMilliSeconds; + property AsDateTime: TDateTime read FValue write FValue; + end; + + THeaderNames = class + public const + HEADER_TYPE = 'typ'; + ALGORITHM = 'alg'; + KEY_ID = 'kid'; + end; + TJOSEBase = class private - function GetEncoded: TSuperBytes; - function GetURLEncoded: TSuperBytes; - procedure SetEncoded(const Value: TSuperBytes); - procedure SetURLEncoded(const Value: TSuperBytes); + function GetEncoded: TJOSEBytes; + function GetURLEncoded: TJOSEBytes; + procedure SetEncoded(const Value: TJOSEBytes); + procedure SetURLEncoded(const Value: TJOSEBytes); protected FJSON: TJSONObject; -// procedure AddPairOfType(const AName: string; const AValue: T); + procedure AddPairOfType(const AName: string; const AValue: T); public constructor Create; destructor Destroy; override; + function Clone: TJSONObject; + property JSON: TJSONObject read FJSON write FJSON; - property Encoded: TSuperBytes read GetEncoded write SetEncoded; - property URLEncoded: TSuperBytes read GetURLEncoded write SetURLEncoded; + property Encoded: TJOSEBytes read GetEncoded write SetEncoded; + property URLEncoded: TJOSEBytes read GetURLEncoded write SetURLEncoded; end; +function ToJSON(Value: TJSONAncestor): string; +function JSONDate(ADate: TDateTime): Int64; implementation uses - JOSE.Encoding.Base64 - {$ifdef DelphiXE7_UP}, System.JSON {$endif} -; + System.DateUtils, + System.JSON, + JOSE.Encoding.Base64; + +{$IF CompilerVersion >= 28} +function ToJSON(Value: TJSONAncestor): string; +begin + Result := Value.ToJson; +end; +{$ELSE} +function ToJSON(Value: TJSONAncestor): string; +var + LBytes: TBytes; + LLen: Integer; +begin + SetLength(LBytes, Value.EstimatedByteSize); + LLen := Value.ToBytes(LBytes, 0); + Result := TEncoding.UTF8.GetString(LBytes, 0, LLen); +end; +{$IFEND} + +function JSONDate(ADate: TDateTime): Int64; +begin + Result := DateTimeToUnix(ADate, False); +end; { TJOSEBase } +function TJOSEBase.Clone: TJSONObject; +begin + Result := FJSON.Clone as TJSONObject; +end; + constructor TJOSEBase.Create; begin FJSON := TJSONObject.Create; @@ -83,28 +157,27 @@ destructor TJOSEBase.Destroy; inherited; end; -function TJOSEBase.GetEncoded: TSuperBytes; +function TJOSEBase.GetEncoded: TJOSEBytes; begin - Result := TBase64.Encode(FJSON.ToJSON); + Result := TBase64.Encode(ToJSON(FJSON)); end; -function TJOSEBase.GetURLEncoded: TSuperBytes; +function TJOSEBase.GetURLEncoded: TJOSEBytes; begin - Result := TBase64.URLEncode(FJSON.ToJSON); + Result := TBase64.URLEncode(ToJSON(FJSON)); end; -procedure TJOSEBase.SetEncoded(const Value: TSuperBytes); +procedure TJOSEBase.SetEncoded(const Value: TJOSEBytes); var - LJSONStr: TSuperBytes; + LJSONStr: TJOSEBytes; begin LJSONStr := TBase64.Decode(Value); FJSON.Parse(LJSONStr, 0) - end; -procedure TJOSEBase.SetURLEncoded(const Value: TSuperBytes); +procedure TJOSEBase.SetURLEncoded(const Value: TJOSEBytes); var - LJSONStr: TSuperBytes; + LJSONStr: TJOSEBytes; LValue: TJSONValue; begin LJSONStr := TBase64.URLDecode(Value); @@ -115,12 +188,147 @@ procedure TJOSEBase.SetURLEncoded(const Value: TSuperBytes); FJSON.Free; FJSON := LValue as TJSONObject; end; +end; + +procedure TJOSEBase.AddPairOfType(const AName: string; const AValue: T); +begin + TJSONUtils.SetJSONValueFrom(AName, AValue, FJSON); +end; + +{ TJOSENumericDate } + +procedure TJOSENumericDate.AddSeconds(ASeconds: Int64); +begin + FValue := System.DateUtils.IncSecond(FValue, ASeconds); +end; + +constructor TJOSENumericDate.Create(AValue: TDateTime); +begin + FValue := AValue; +end; + +class function TJOSENumericDate.FromMilliseconds(AMillisecondsFromEpoch: Int64): TJOSENumericDate; +begin + Result := TJOSENumericDate.Create(UnixToDateTime(AMillisecondsFromEpoch div CONVERSION, False)); +end; + +class function TJOSENumericDate.FromSeconds(ASecondsFromEpoch: Int64): TJOSENumericDate; +begin + Result := TJOSENumericDate.Create(UnixToDateTime(ASecondsFromEpoch, False)); +end; + +function TJOSENumericDate.GetAsMilliSeconds: Int64; +begin + Result := DateTimeToUnix(FValue, False) * CONVERSION; +end; + +function TJOSENumericDate.GetAsSeconds: Int64; +begin + Result := DateTimeToUnix(FValue, False); +end; + +function TJOSENumericDate.IsAfter(const AWhen: TJOSENumericDate; ASkewSeconds: Integer): Boolean; +begin + Result := ((Self.AsSeconds - ASkewSeconds) > AWhen.AsSeconds); +end; + +function TJOSENumericDate.IsBefore(const AWhen: TJOSENumericDate; ASkewSeconds: Integer): Boolean; +begin + Result := ((Self.AsSeconds + ASkewSeconds) < AWhen.AsSeconds); +end; + +function TJOSENumericDate.IsOnOrAfter(const AWhen: TJOSENumericDate; ASkewSeconds: Integer): Boolean; +begin + Result := ((Self.AsSeconds - ASkewSeconds) >= AWhen.AsSeconds); +end; + +procedure TJOSENumericDate.SetAsSeconds(const AValue: Int64); +begin + FValue := UnixToDateTime(AValue); +end; + +{ TJOSETimeUnitHelper } + +function TJOSETimeUnitHelper.Convert(ADuration: Cardinal; ADestUnit: TJOSETimeUnit): Cardinal; +begin + Result := 0; + case Self of + TJOSETimeUnit.Days: + begin + case ADestUnit of + TJOSETimeUnit.Days: Result := ADuration; + TJOSETimeUnit.Hours: Result := ADuration * 24; + TJOSETimeUnit.Minutes: Result := ADuration * 24 * 60; + TJOSETimeUnit.Seconds: Result := ADuration * 24 * 60 * 60; + TJOSETimeUnit.Milliseconds: Result := ADuration * 24 * 60 * 60 * 1000; + end; + end; + TJOSETimeUnit.Hours: + begin + case ADestUnit of + TJOSETimeUnit.Days: Result := ADuration div 24; + TJOSETimeUnit.Hours: Result := ADuration; + TJOSETimeUnit.Minutes: Result := ADuration * 60; + TJOSETimeUnit.Seconds: Result := ADuration * 60 * 60; + TJOSETimeUnit.Milliseconds: Result := ADuration * 60 * 60 * 1000; + end; + end; + TJOSETimeUnit.Minutes: + begin + case ADestUnit of + TJOSETimeUnit.Days: Result := (ADuration div 24) div 60; + TJOSETimeUnit.Hours: Result := ADuration div 60; + TJOSETimeUnit.Minutes: Result := ADuration; + TJOSETimeUnit.Seconds: Result := ADuration * 60; + TJOSETimeUnit.Milliseconds: Result := ADuration * 60 * 1000; + end; + end; + TJOSETimeUnit.Seconds: + begin + case ADestUnit of + TJOSETimeUnit.Days: Result := ((ADuration div 24) div 60) div 60; + TJOSETimeUnit.Hours: Result := (ADuration div 24) div 60; + TJOSETimeUnit.Minutes: Result := ADuration div 24; + TJOSETimeUnit.Seconds: Result := ADuration; + TJOSETimeUnit.Milliseconds: Result := ADuration * 1000; + end; + end; + TJOSETimeUnit.Milliseconds: + begin + case ADestUnit of + TJOSETimeUnit.Days: Result := (((ADuration div 24) div 60) div 60) div 1000; + TJOSETimeUnit.Hours: Result := ((ADuration div 24) div 60) div 60; + TJOSETimeUnit.Minutes: Result := (ADuration div 24) div 60; + TJOSETimeUnit.Seconds: Result := ADuration div 24; + TJOSETimeUnit.Milliseconds: Result := ADuration; + end; + end; + end; +end; + +function TJOSETimeUnitHelper.ToDays(ADuration: Cardinal): Cardinal; +begin + Result := Convert(ADuration, TJOSETimeUnit.Days); +end; + +function TJOSETimeUnitHelper.ToHours(ADuration: Cardinal): Cardinal; +begin + Result := Convert(ADuration, TJOSETimeUnit.Hours); +end; + +function TJOSETimeUnitHelper.ToMilliseconds(ADuration: Cardinal): Cardinal; +begin + Result := Convert(ADuration, TJOSETimeUnit.Milliseconds); +end; +function TJOSETimeUnitHelper.ToMinutes(ADuration: Cardinal): Cardinal; +begin + Result := Convert(ADuration, TJOSETimeUnit.Minutes); end; -//procedure TJOSEBase.AddPairOfType(const AName: string; const AValue: T); -//begin -// TJSONUtils.SetJSONValueFrom(AName, AValue, FJSON); -//end; +function TJOSETimeUnitHelper.ToSeconds(ADuration: Cardinal): Cardinal; +begin + Result := Convert(ADuration, TJOSETimeUnit.Seconds); +end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Builder.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Builder.pas index 4e32822b..fb01d0a8 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Builder.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Builder.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -19,16 +19,12 @@ { limitations under the License. } { } {******************************************************************************} - -/// -/// Utility class to encode and decode a JWT -/// unit JOSE.Core.Builder; interface uses - SysUtils, + System.SysUtils, JOSE.Types.Bytes, JOSE.Core.Base, JOSE.Core.Parts, @@ -39,34 +35,57 @@ interface JOSE.Core.JWE; type + /// + /// Utility class to encode and decode a JWT + /// TJOSE = class private - class function DeserialzeVerify(AKey: TJWK; ACompactToken: TSuperBytes; AVerify: Boolean): TJWT; + class function DeserializeVerify(AKey: TJWK; const ACompactToken: TJOSEBytes; AVerify: Boolean; AClaimsClass: TJWTClaimsClass): TJWT; public - class function Sign(AKey: TJWK; AAlg: TJWAEnum; AToken: TJWT): TSuperBytes; - class function Verify(AKey: TJWK; ACompactToken: TSuperBytes): TJWT; + class function Sign(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; + class function Verify(AKey: TJWK; const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass = nil): TJWT; overload; + class function Verify(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass = nil): TJWT; overload; + + class function SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT; ASkipValidation: Boolean): TJOSEBytes; overload; + class function SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; overload; + class function SerializeCompact(AKey: TJOSEBytes; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; overload; - class function SerializeCompact(AKey: TJWK; AAlg: TJWAEnum; AToken: TJWT): TSuperBytes; - class function DeserializeCompact(const ACompactToken: TSuperBytes): TJWT; + class function DeserializeCompact(AKey: TJWK; const ACompactToken: TJOSEBytes): TJWT; overload; + class function DeserializeCompact(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes): TJWT; overload; - class function SHA256CompactToken(AKey: TSuperBytes; AToken: TJWT): TSuperBytes; + class function SHA256CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes; + class function SHA284CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes; + class function SHA512CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes; end; implementation uses - Types, - StrUtils; + System.Types, + System.StrUtils; { TJOSE } -class function TJOSE.DeserializeCompact(const ACompactToken: TSuperBytes): TJWT; +class function TJOSE.DeserializeCompact(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes): TJWT; +var + LKey: TJWK; begin - Result := DeserialzeVerify(nil, ACompactToken, True); + LKey := TJWK.Create(AKey); + try + Result := DeserializeVerify(LKey, ACompactToken, True, TJWTClaims); + finally + LKey.Free; + end; +end; + +class function TJOSE.DeserializeCompact(AKey: TJWK; const ACompactToken: TJOSEBytes): TJWT; +begin + Result := DeserializeVerify(AKey, ACompactToken, True, TJWTClaims); end; -class function TJOSE.DeserialzeVerify(AKey: TJWK; ACompactToken: TSuperBytes; AVerify: Boolean): TJWT; +class function TJOSE.DeserializeVerify(AKey: TJWK; const ACompactToken: TJOSEBytes; + AVerify: Boolean; AClaimsClass: TJWTClaimsClass): TJWT; var LRes: TStringDynArray; LSigner: TJWS; @@ -77,14 +96,15 @@ class function TJOSE.DeserialzeVerify(AKey: TJWK; ACompactToken: TSuperBytes; AV case Length(LRes) of 3: begin - Result := TJWT.Create(TJWTClaims); + Result := TJWT.Create(AClaimsClass); try LSigner := TJWS.Create(Result); + LSigner.SkipKeyValidation := True; try + LSigner.SetKey(AKey); + LSigner.CompactToken := ACompactToken; if AVerify then - LSigner.Verify(AKey, ACompactToken) - else - LSigner.CompactToken := ACompactToken; + LSigner.VerifySignature; finally LSigner.Free; end; @@ -101,50 +121,84 @@ class function TJOSE.DeserialzeVerify(AKey: TJWK; ACompactToken: TSuperBytes; AV end end; -class function TJOSE.SerializeCompact(AKey: TJWK; AAlg: TJWAEnum; AToken: TJWT): TSuperBytes; +class function TJOSE.SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; +begin + Result := SerializeCompact(AKey, AAlg, AToken, True); +end; + +class function TJOSE.Sign(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; var LSigner: TJWS; begin LSigner := TJWS.Create(AToken); try - LSigner.Sign(AKey, AAlg); - Result := LSigner.CompactToken; + Result := LSigner.Sign(AKey, AAlg); finally LSigner.Free; end; end; -class function TJOSE.Sign(AKey: TJWK; AAlg: TJWAEnum; AToken: TJWT): TSuperBytes; +class function TJOSE.SerializeCompact(AKey: TJOSEBytes; AAlg: TJOSEAlgorithmId; AToken: TJWT): TJOSEBytes; var - LSigner: TJWS; + LKey: TJWK; begin - LSigner := TJWS.Create(AToken); + LKey := TJWK.Create(AKey); try - Result := LSigner.Sign(AKey, AAlg); + Result := SerializeCompact(LKey, AAlg, AToken, True); finally - LSigner.Free; + LKey.Free; end; end; -class function TJOSE.SHA256CompactToken(AKey: TSuperBytes; AToken: TJWT): TSuperBytes; +class function TJOSE.SerializeCompact(AKey: TJWK; AAlg: TJOSEAlgorithmId; AToken: TJWT; + ASkipValidation: Boolean): TJOSEBytes; var LSigner: TJWS; - LKey: TJWK; begin LSigner := TJWS.Create(AToken); - LKey := TJWK.Create(AKey); try - LSigner.Sign(LKey, HS256); + LSigner.SkipKeyValidation := ASkipValidation; + LSigner.Sign(AKey, AAlg); Result := LSigner.CompactToken; finally - LKey.Free; LSigner.Free; end; end; -class function TJOSE.Verify(AKey: TJWK; ACompactToken: TSuperBytes): TJWT; +class function TJOSE.SHA256CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes; +begin + Result := SerializeCompact(AKey, TJOSEAlgorithmId.HS256, AToken); +end; + +class function TJOSE.SHA284CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes; +begin + Result := SerializeCompact(AKey, TJOSEAlgorithmId.HS384, AToken); +end; + +class function TJOSE.SHA512CompactToken(AKey: TJOSEBytes; AToken: TJWT): TJOSEBytes; +begin + Result := SerializeCompact(AKey, TJOSEAlgorithmId.HS512, AToken); +end; + +class function TJOSE.Verify(AKey: TJWK; const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass): TJWT; +begin + if not Assigned(AClaimsClass) then + AClaimsClass := TJWTClaims; + + Result := DeserializeVerify(AKey, ACompactToken, True, AClaimsClass); +end; + +class function TJOSE.Verify(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes; + AClaimsClass: TJWTClaimsClass): TJWT; +var + LKey: TJWK; begin - Result := DeserialzeVerify(AKey, ACompactToken, True); + LKey := TJWK.Create(AKey); + try + Result := Verify(LKey, ACompactToken, AClaimsClass); + finally + LKey.Free; + end; end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Compression.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Compression.pas new file mode 100644 index 00000000..6e64aab7 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Compression.pas @@ -0,0 +1,47 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// JSON Web Algorithms (JWA) RFC implementation (partial)
+///
+/// +/// JWA RFC Document +/// +unit JOSE.Core.JWA.Compression; + +interface + +uses + System.SysUtils, + JOSE.Types.Bytes, + JOSE.Core.JWA; + +type + IJOSECompressionAlgorithm = interface(IJOSEAlgorithm) + ['{B2782386-F5A2-43BF-B86C-B103A0221FC4}'] + function Compress(const Data: TBytes): TBytes; + function Decompress(const CompressedData: TBytes): TBytes; + end; + +implementation + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Encryption.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Encryption.pas new file mode 100644 index 00000000..79b4dc9e --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Encryption.pas @@ -0,0 +1,70 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// JSON Web Encryption (JWE) RFC implementation (initial) +/// +/// +/// JWE RFC Document +/// +unit JOSE.Core.JWA.Encryption; + +interface + +uses + System.SysUtils, + JOSE.Types.Bytes, + JOSE.Core.JWA; + +type + TEncryptionParts = class + private + FAuthenticationTag: TBytes; + FCiphertext: TBytes; + FIv: TBytes; + public + constructor Create(const AIv,ACiphertext, AAuthenticationTag: TBytes); + + property Iv: TBytes read FIv; + property Ciphertext: TBytes read FCiphertext; + property AuthenticationTag: TBytes read FAuthenticationTag; + end; + + IJOSEEncryptionAlgorithm = interface(IJOSEAlgorithm) + ['{D802ABF3-82E2-494B-B96A-D13C5A782574}'] + //function GetContentEncryptionKeyDescriptor: TContentEncryptionKeyDescriptor; + function Encrypt(const APlaintext, AAdditionalData, AContentEncryptionKey, IvOverride: TBytes{; AHeaders headers}): TEncryptionParts; + function Decrypt(AEncryptionParts: TEncryptionParts; const AAdditionalData, AContentEncryptionKey: TBytes{; Headers headers}): TBytes; + end; + +implementation + +{ TEncryptionParts } + +constructor TEncryptionParts.Create(const AIv, ACiphertext, AAuthenticationTag: TBytes); +begin + FIv := AIv; + FCiphertext := ACiphertext; + FAuthenticationTag := AAuthenticationTag; +end; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Factory.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Factory.pas new file mode 100644 index 00000000..fa305f0c --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Factory.pas @@ -0,0 +1,163 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// JSON Web Algorithms (JWA) RFC implementation (partial)
+///
+/// +/// JWA RFC Document +/// +unit JOSE.Core.JWA.Factory; + +interface + +uses + System.SysUtils, + System.Generics.Defaults, + System.Generics.Collections, + JOSE.Types.Bytes, + JOSE.Core.JWA, + JOSE.Core.JWA.Signing, + JOSE.Core.JWA.Compression, + JOSE.Core.JWA.Encryption; + +type + TJOSEAlgorithmRegistry = class + private + FName: string; + FAlgorithms: TDictionary; + public + constructor Create(const AName: string); + destructor Destroy; override; + + function GetAlgorithm(AAlgorithmIdentifier: string): T; + + function RegisterAlgorithm(AAlgorithm: T): TJOSEAlgorithmRegistry; + procedure UnregisterAlgorithm(AAlgId: string); overload; + procedure UnregisterAlgorithm(AAlgId: TJOSEAlgorithmId); overload; + end; + + TJOSEAlgorithmRegistryFactory = class + private class var + FInstance: TJOSEAlgorithmRegistryFactory; + private + FSigningAlgorithmRegistry: TJOSEAlgorithmRegistry; + FEncryptionAlgorithmRegistry: TJOSEAlgorithmRegistry; + FCompressionAlgorithmRegistry: TJOSEAlgorithmRegistry; + + constructor Create; + + class function GetInstance: TJOSEAlgorithmRegistryFactory; static; + public + destructor Destroy; override; + + property SigningAlgorithmRegistry: TJOSEAlgorithmRegistry + read FSigningAlgorithmRegistry; + property EncryptionAlgorithmRegistry: TJOSEAlgorithmRegistry + read FEncryptionAlgorithmRegistry; + property CompressionAlgorithmRegistry: TJOSEAlgorithmRegistry + read FCompressionAlgorithmRegistry; + + class property Instance: TJOSEAlgorithmRegistryFactory read GetInstance; + end; + + + +implementation + +{ TJOSEAlgorithmRegistry } + +constructor TJOSEAlgorithmRegistry.Create(const AName: string); +begin + FAlgorithms := TDictionary.Create; + FName := AName; +end; + +destructor TJOSEAlgorithmRegistry.Destroy; +begin + FAlgorithms.Free; + inherited; +end; + +function TJOSEAlgorithmRegistry.GetAlgorithm(AAlgorithmIdentifier: string): T; +begin + FAlgorithms.TryGetValue(AAlgorithmIdentifier, Result); +end; + +function TJOSEAlgorithmRegistry.RegisterAlgorithm(AAlgorithm: T): TJOSEAlgorithmRegistry; +begin + FAlgorithms.Add(AAlgorithm.GetAlgorithmIdentifier.AsString, AAlgorithm); + Result := Self; +end; + +procedure TJOSEAlgorithmRegistry.UnregisterAlgorithm(AAlgId: string); +begin + FAlgorithms.Remove(AAlgId); +end; + +procedure TJOSEAlgorithmRegistry.UnregisterAlgorithm(AAlgId: TJOSEAlgorithmId); +begin + UnregisterAlgorithm(AAlgId.AsString); +end; + +{ TJOSEAlgorithmRegistryFactory } + +constructor TJOSEAlgorithmRegistryFactory.Create; +begin + FSigningAlgorithmRegistry := TJOSEAlgorithmRegistry.Create('alg'); + + FSigningAlgorithmRegistry + .RegisterAlgorithm(THmacUsingShaAlgorithm.HmacSha256) + .RegisterAlgorithm(THmacUsingShaAlgorithm.HmacSha384) + .RegisterAlgorithm(THmacUsingShaAlgorithm.HmacSha512) + .RegisterAlgorithm(TRSAUsingShaAlgorithm.RSA256) + .RegisterAlgorithm(TRSAUsingShaAlgorithm.RSA384) + .RegisterAlgorithm(TRSAUsingShaAlgorithm.RSA512) + ; + + FEncryptionAlgorithmRegistry := TJOSEAlgorithmRegistry.Create('alg'); + + FCompressionAlgorithmRegistry := TJOSEAlgorithmRegistry.Create('alg'); +end; + +destructor TJOSEAlgorithmRegistryFactory.Destroy; +begin + FSigningAlgorithmRegistry.Free; + FEncryptionAlgorithmRegistry.Free; + FCompressionAlgorithmRegistry.Free; + + inherited; +end; + +class function TJOSEAlgorithmRegistryFactory.GetInstance: TJOSEAlgorithmRegistryFactory; +begin + if not Assigned(FInstance) then + FInstance := TJOSEAlgorithmRegistryFactory.Create; + Result := FInstance; +end; + +initialization + +finalization + TJOSEAlgorithmRegistryFactory.FInstance.Free; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Signing.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Signing.pas new file mode 100644 index 00000000..357e6120 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.Signing.pas @@ -0,0 +1,306 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// JSON Web Algorithms (JWA) RFC implementation (partial)
+///
+/// +/// JWA RFC Document +/// +unit JOSE.Core.JWA.Signing; + +interface + +uses + System.SysUtils, + JOSE.Types.Bytes, + JOSE.Hashing.HMAC, + JOSE.Signing.RSA, + JOSE.Core.Base, + JOSE.Core.Parts, + JOSE.Core.JWA, + JOSE.Core.JWK; + +type + IJOSESigningAlgorithm = interface(IJOSEAlgorithm) + ['{F999E708-40F5-40E3-81F9-C4D20EB2FA79}'] + function VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; + function Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; + procedure ValidateSigningKey(const AKey: TJOSEBytes); + procedure ValidateVerificationKey(const AKey: TJOSEBytes); + end; + + TBaseSignatureAlgorithm = class(TJOSEAlgorithm, IJOSESigningAlgorithm) + public + function VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; + function Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; + procedure ValidateSigningKey(const AKey: TJOSEBytes); + procedure ValidateVerificationKey(const AKey: TJOSEBytes); + end; + + TUnsecureNoneAlgorithm = class(TJOSEAlgorithm, IJOSESigningAlgorithm) + private + const CANNOT_HAVE_KEY = 'Unsecured JWS (%s=%s) must not use a key'; + procedure ValidateKey(const AKey: TJOSEBytes); + public + constructor Create; + function VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; + function Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; + procedure ValidateSigningKey(const AKey: TJOSEBytes); + procedure ValidateVerificationKey(const AKey: TJOSEBytes); + end; + + THmacUsingShaAlgorithm = class(TJOSEAlgorithm, IJOSESigningAlgorithm) + private + FKeyMinLength: Integer; + protected + FHMACAlgorithm: THMACAlgorithm; + constructor Create(const AAlgorithmId: TJOSEAlgorithmId; AKeyMinLength: Integer); + procedure ValidateKey(const AKey: TJOSEBytes); + public + class function HmacSha256: IJOSESigningAlgorithm; + class function HmacSha384: IJOSESigningAlgorithm; + class function HmacSha512: IJOSESigningAlgorithm; + + function VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; + function Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; + procedure ValidateSigningKey(const AKey: TJOSEBytes); + procedure ValidateVerificationKey(const AKey: TJOSEBytes); + end; + + TRSAUsingSHAAlgorithm = class(TJOSEAlgorithm, IJOSESigningAlgorithm) + private + FKeyMinLength: Integer; + protected + FRSAAlgorithm: TRSAAlgorithm; + constructor Create(const AAlgorithmId: TJOSEAlgorithmId; AKeyMinLength: Integer); + public + class function RSA256: IJOSESigningAlgorithm; + class function RSA384: IJOSESigningAlgorithm; + class function RSA512: IJOSESigningAlgorithm; + + function VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; + function Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; + procedure ValidateSigningKey(const AKey: TJOSEBytes); + procedure ValidateVerificationKey(const AKey: TJOSEBytes); + end; + +implementation + +uses + System.Types, + System.StrUtils, + JOSE.Encoding.Base64; + +constructor THmacUsingShaAlgorithm.Create(const AAlgorithmId: TJOSEAlgorithmId; AKeyMinLength: Integer); +begin + FAlgorithmIdentifier := AAlgorithmId; + + case AAlgorithmId of + TJOSEAlgorithmId.HS256: FHMACAlgorithm := THMACAlgorithm.SHA256; + TJOSEAlgorithmId.HS384: FHMACAlgorithm := THMACAlgorithm.SHA384; + TJOSEAlgorithmId.HS512: FHMACAlgorithm := THMACAlgorithm.SHA512; + end; + FKeyCategory := TJOSEKeyCategory.Symmetric; + FKeyType := 'oct'; + FKeyMinLength := AKeyMinLength; +end; + +class function THmacUsingShaAlgorithm.HmacSha256: IJOSESigningAlgorithm; +begin + Result := THmacUsingShaAlgorithm.Create(TJOSEAlgorithmId.HS256, 256); +end; + +class function THmacUsingShaAlgorithm.HmacSha384: IJOSESigningAlgorithm; +begin + Result := THmacUsingShaAlgorithm.Create(TJOSEAlgorithmId.HS384, 384); +end; + +class function THmacUsingShaAlgorithm.HmacSha512: IJOSESigningAlgorithm; +begin + Result := THmacUsingShaAlgorithm.Create(TJOSEAlgorithmId.HS512, 512); +end; + +function THmacUsingShaAlgorithm.Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; +var + LSign: TJOSEBytes; +begin + LSign := THMAC.Sign(AInput, AKey, FHMACAlgorithm); + Result := TBase64.URLEncode(LSign.AsBytes); +end; + +procedure THmacUsingShaAlgorithm.ValidateKey(const AKey: TJOSEBytes); +begin + if AKey.IsEmpty then + raise EJOSEException.Create('Key is null'); + + if AKey.Size * 8 < FKeyMinLength then + raise EJOSEException.CreateFmt('Key is too short (%dbit), expected (%dbit)', + [AKey.Size * 8, FKeyMinLength]); +end; + +procedure THmacUsingShaAlgorithm.ValidateSigningKey(const AKey: TJOSEBytes); +begin + ValidateKey(AKey); +end; + +procedure THmacUsingShaAlgorithm.ValidateVerificationKey(const AKey: TJOSEBytes); +begin + ValidateKey(AKey); +end; + +function THmacUsingShaAlgorithm.VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; +var + LComputedSignature: TJOSEBytes; +begin + LComputedSignature := THMAC.Sign(AInput, AKey, FHMACAlgorithm); + LComputedSignature := TBase64.URLEncode(LComputedSignature.AsBytes); + + Result := LComputedSignature = ASignature; +end; + +{ TBaseSignatureAlgorithm } + +function TBaseSignatureAlgorithm.Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; +begin + Result := ''; +end; + +procedure TBaseSignatureAlgorithm.ValidateSigningKey(const AKey: TJOSEBytes); +begin + raise EJOSEException.Create('Not implemented'); +end; + +procedure TBaseSignatureAlgorithm.ValidateVerificationKey(const AKey: TJOSEBytes); +begin + raise EJOSEException.Create('Not implemented'); +end; + +function TBaseSignatureAlgorithm.VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; +begin + Result := False; +end; + +{ TUnsecureNoneAlgorithm } + +constructor TUnsecureNoneAlgorithm.Create; +begin + FAlgorithmIdentifier := TJOSEAlgorithmId.None; + FKeyCategory := TJOSEKeyCategory.None; +end; + +function TUnsecureNoneAlgorithm.Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; +begin + ValidateKey(AKey); + Result := TJOSEBytes.Empty; +end; + +procedure TUnsecureNoneAlgorithm.ValidateKey(const AKey: TJOSEBytes); +begin + if not AKey.IsEmpty then + raise EJOSEException.Create(Format(CANNOT_HAVE_KEY, + [THeaderNames.ALGORITHM, TJOSEAlgorithmId.None.AsString])); +end; + +procedure TUnsecureNoneAlgorithm.ValidateSigningKey(const AKey: TJOSEBytes); +begin + ValidateKey(AKey); +end; + +procedure TUnsecureNoneAlgorithm.ValidateVerificationKey(const AKey: TJOSEBytes); +begin + ValidateKey(AKey); +end; + +function TUnsecureNoneAlgorithm.VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; +begin + ValidateKey(AKey); + Result := ASignature.IsEmpty; +end; + +{ TRSAAlgorithm } + +constructor TRSAUsingSHAAlgorithm.Create(const AAlgorithmId: TJOSEAlgorithmId; AKeyMinLength: Integer); +begin + FAlgorithmIdentifier := AAlgorithmId; + + case AAlgorithmId of + TJOSEAlgorithmId.RS256: FRSAAlgorithm := TRSAAlgorithm.RS256; + TJOSEAlgorithmId.RS384: FRSAAlgorithm := TRSAAlgorithm.RS384; + TJOSEAlgorithmId.RS512: FRSAAlgorithm := TRSAAlgorithm.RS512; + end; + FKeyCategory := TJOSEKeyCategory.Asymmetric; + FKeyType := 'pem'; + FKeyMinLength := AKeyMinLength; +end; + +class function TRSAUsingSHAAlgorithm.RSA256: IJOSESigningAlgorithm; +begin + Result := TRSAUsingSHAAlgorithm.Create(TJOSEAlgorithmId.RS256, 256); +end; + +class function TRSAUsingSHAAlgorithm.RSA384: IJOSESigningAlgorithm; +begin + Result := TRSAUsingSHAAlgorithm.Create(TJOSEAlgorithmId.RS384, 384); +end; + +class function TRSAUsingSHAAlgorithm.RSA512: IJOSESigningAlgorithm; +begin + Result := TRSAUsingSHAAlgorithm.Create(TJOSEAlgorithmId.RS512, 512); +end; + +function TRSAUsingSHAAlgorithm.Sign(const AKey, AInput: TJOSEBytes): TJOSEBytes; +var + LSign: TJOSEBytes; +begin + LSign := TRSA.Sign(AInput, AKey, FRSAAlgorithm); + Result := TBase64.URLEncode(LSign.AsBytes); +end; + +procedure TRSAUsingSHAAlgorithm.ValidateSigningKey(const AKey: TJOSEBytes); +begin + if AKey.IsEmpty then + raise EJOSEException.Create('Key is null'); + + if not TRSA.VerifyPrivateKey(AKey) then + raise EJOSEException.Create('Key is not RSA key in PEM format'); +end; + +procedure TRSAUsingSHAAlgorithm.ValidateVerificationKey(const AKey: TJOSEBytes); +begin + if AKey.IsEmpty then + raise EJOSEException.Create('Key is null'); + + if not TRSA.VerifyPublicKey(AKey) then + raise EJOSEException.Create('Key is not RSA key in PEM format'); +end; + +function TRSAUsingSHAAlgorithm.VerifySignature(const AKey, AInput, ASignature: TJOSEBytes): Boolean; +var + LDecodedSignature: TJOSEBytes; +begin + ValidateVerificationKey(AKey); + LDecodedSignature := TBase64.URLDecode(ASignature); + Result := TRSA.Verify(AInput, LDecodedSignature, AKey, FRSAAlgorithm); +end; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.pas index ef4b1c74..6760c4d0 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWA.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -28,98 +28,133 @@ /// unit JOSE.Core.JWA; -{$I ..\..\..\Source\Mars.inc} - interface +uses + System.SysUtils, + System.Generics.Defaults, + System.Generics.Collections, + JOSE.Types.Bytes; + type - TJWAEnum = (None, HS256, HS384, HS512, RS256, RS348, RS512); + {$SCOPEDENUMS ON} + TJOSEKeyCategory = (None, Symmetric, Asymmetric); - {$ifdef DelphiXE8_UP} - TJWAEnumHelper = record helper for TJWAEnum + TJOSEAlgorithmId = ( + Unknown, None, + HS256, HS384, HS512, + RS256, RS384, RS512, + ES256, ES384, ES512, + PS256, PS384, PS512 + ); + TJOSEAlgorithmIdHelper = record helper for TJOSEAlgorithmId private function GetAsString: string; procedure SetAsString(const Value: string); public property AsString: string read GetAsString write SetAsString; end; - {$else} - TJWAEnumHelper = class + + IJOSEAlgorithm = interface + ['{1BA290C7-D139-4CD8-86FE-7F80B9826007}'] + function GetAlgorithmIdentifier: TJOSEAlgorithmId; + function GetKeyCategory: TJOSEKeyCategory; + function GetKeyType: string; + end; + + IJOSEKeyManagementAlgorithm = interface(IJOSEAlgorithm) + ['{2A2FBF37-8267-4DA3-AA11-7E1C3ED235DD}'] + end; + + TJOSEAlgorithm = class(TInterfacedObject, IJOSEAlgorithm) + FAlgorithmIdentifier: TJOSEAlgorithmId; + FKeyCategory: TJOSEKeyCategory; + FKeyType: string; public - class function AsString(AEnum: TJWAEnum): string; static; - class function FromString(AString: string): TJWAEnum; static; + function GetAlgorithmIdentifier: TJOSEAlgorithmId; + function GetKeyCategory: TJOSEKeyCategory; + function GetKeyType: string; end; - {$endif} implementation -{$ifdef DelphiXE8_UP} -function TJWAEnumHelper.GetAsString: string; +{ TJOSEAlgorithm } + +function TJOSEAlgorithm.GetAlgorithmIdentifier: TJOSEAlgorithmId; +begin + Result := FAlgorithmIdentifier; +end; + +function TJOSEAlgorithm.GetKeyCategory: TJOSEKeyCategory; +begin + Result := FKeyCategory; +end; + +function TJOSEAlgorithm.GetKeyType: string; +begin + Result := FKeyType; +end; + +{ TJOSEAlgorithmIdHelper } + +function TJOSEAlgorithmIdHelper.GetAsString: string; begin case Self of - None: Result := 'none'; - HS256: Result := 'HS256'; - HS384: Result := 'HS384'; - HS512: Result := 'HS512'; - RS256: Result := 'RS256'; - RS348: Result := 'RS348'; - RS512: Result := 'RS512'; + TJOSEAlgorithmId.None: Result := 'none'; + + TJOSEAlgorithmId.HS256: Result := 'HS256'; + TJOSEAlgorithmId.HS384: Result := 'HS384'; + TJOSEAlgorithmId.HS512: Result := 'HS512'; + + TJOSEAlgorithmId.RS256: Result := 'RS256'; + TJOSEAlgorithmId.RS384: Result := 'RS384'; + TJOSEAlgorithmId.RS512: Result := 'RS512'; + + TJOSEAlgorithmId.ES256: Result := 'ES256'; + TJOSEAlgorithmId.ES384: Result := 'ES384'; + TJOSEAlgorithmId.ES512: Result := 'ES512'; + + TJOSEAlgorithmId.PS256: Result := 'PS256'; + TJOSEAlgorithmId.PS384: Result := 'PS384'; + TJOSEAlgorithmId.PS512: Result := 'PS512'; end; end; -procedure TJWAEnumHelper.SetAsString(const Value: string); +procedure TJOSEAlgorithmIdHelper.SetAsString(const Value: string); begin if Value = 'none' then - Self := None + Self := TJOSEAlgorithmId.None + else if Value = 'HS256' then - Self := HS256 + Self := TJOSEAlgorithmId.HS256 else if Value = 'HS384' then - Self := HS384 + Self := TJOSEAlgorithmId.HS384 else if Value = 'HS512' then - Self := HS512 + Self := TJOSEAlgorithmId.HS512 + else if Value = 'RS256' then - Self := RS256 - else if Value = 'RS348' then - Self := RS348 + Self := TJOSEAlgorithmId.RS256 + else if Value = 'RS384' then + Self := TJOSEAlgorithmId.RS384 else if Value = 'RS512' then - Self := RS512; -end; -{$else} + Self := TJOSEAlgorithmId.RS512 + else if Value = 'ES256' then + Self := TJOSEAlgorithmId.ES256 + else if Value = 'ES384' then + Self := TJOSEAlgorithmId.ES384 + else if Value = 'ES512' then + Self := TJOSEAlgorithmId.ES512 -{ TJWAEnumHelper } + else if Value = 'PS256' then + Self := TJOSEAlgorithmId.PS256 + else if Value = 'PS384' then + Self := TJOSEAlgorithmId.PS384 + else if Value = 'PS512' then + Self := TJOSEAlgorithmId.PS512 -class function TJWAEnumHelper.FromString(AString: string): TJWAEnum; -begin - if AString = 'none' then - Result := None - else if AString = 'HS256' then - Result := HS256 - else if AString = 'HS384' then - Result := HS384 - else if AString = 'HS512' then - Result := HS512 - else if AString = 'RS256' then - Result := RS256 - else if AString = 'RS348' then - Result := RS348 - else if AString = 'RS512' then - Result := RS512; + else + Self := TJOSEAlgorithmId.Unknown; end; -class function TJWAEnumHelper.AsString(AEnum: TJWAEnum): string; -begin - case AEnum of - None: Result := 'none'; - HS256: Result := 'HS256'; - HS384: Result := 'HS384'; - HS512: Result := 'HS512'; - RS256: Result := 'RS256'; - RS348: Result := 'RS348'; - RS512: Result := 'RS512'; - end; -end; - -{$endif} - end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWE.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWE.pas index 42c57269..f9d5edee 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWE.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWE.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -58,7 +58,7 @@ constructor TJWE.Create(AToken: TJWT); inherited Create(AToken); for LIndex := 0 to COMPACT_PARTS - 1 do - FParts.Add(TSuperBytes.Empty); + FParts.Add(TJOSEBytes.Empty); end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWK.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWK.pas index 05d0954a..1b5475fa 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWK.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWK.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -33,22 +33,23 @@ interface uses SysUtils, JOSE.Types.Bytes, - JOSE.Core.Base; + JOSE.Core.Base, + JOSE.Encoding.Base64; type TJWK = class(TJOSEBase) private - FKey: TSuperBytes; + FKey: TJOSEBytes; public - constructor Create(AKey: TSuperBytes); - property Key: TSuperBytes read FKey write FKey; + constructor Create(AKey: TJOSEBytes); + property Key: TJOSEBytes read FKey write FKey; end; implementation { TJWK } -constructor TJWK.Create(AKey: TSuperBytes); +constructor TJWK.Create(AKey: TJOSEBytes); begin inherited Create; FKey := AKey; diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWS.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWS.pas index 1220afd2..afe5a504 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWS.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWS.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -28,53 +28,64 @@ /// unit JOSE.Core.JWS; -{$I ..\..\..\Source\Mars.inc} - interface uses - SysUtils, + System.SysUtils, JOSE.Types.Bytes, JOSE.Core.Base, JOSE.Core.Parts, JOSE.Core.JWA, JOSE.Core.JWK, - JOSE.Core.JWT; + JOSE.Core.JWT, + JOSE.Core.JWA.Signing; type TJWS = class(TJOSEParts) private const COMPACT_PARTS = 3; - - function GetSigningInput: TSuperBytes; - function GetHeader: TSuperBytes; - function GetPayload: TSuperBytes; - function GetSignature: TSuperBytes; - procedure SetHeader(const Value: TSuperBytes); - procedure SetPayload(const Value: TSuperBytes); - procedure SetSignature(const Value: TSuperBytes); + private + FKey: TJOSEBytes; + private + function GetSigningInput: TJOSEBytes; + function GetHeader: TJOSEBytes; + function GetPayload: TJOSEBytes; + function GetSignature: TJOSEBytes; + procedure SetHeader(const Value: TJOSEBytes); + procedure SetPayload(const Value: TJOSEBytes); + procedure SetSignature(const Value: TJOSEBytes); protected - function GetCompactToken: TSuperBytes; override; - procedure SetCompactToken(const Value: TSuperBytes); override; + function GetAlgorithm(AAlgId: TJOSEAlgorithmId): IJOSESigningAlgorithm; + function GetCompactToken: TJOSEBytes; override; + procedure SetCompactToken(const Value: TJOSEBytes); override; public constructor Create(AToken: TJWT); override; - function Sign(AKey: TJWK; AAlg: TJWAEnum): TSuperBytes; - procedure Verify(AKey: TJWK; const ACompactToken: TSuperBytes); + procedure SetKey(const AKey: TBytes); overload; + procedure SetKey(const AKey: TJOSEBytes); overload; + procedure SetKey(const AKey: TJWK); overload; + + function Sign: TJOSEBytes; overload; + function Sign(AKey: TJWK; AAlgId: TJOSEAlgorithmId): TJOSEBytes; overload; - property Header: TSuperBytes read GetHeader write SetHeader; - property Payload: TSuperBytes read GetPayload write SetPayload; - property Signature: TSuperBytes read GetSignature write SetSignature; - property SigningInput: TSuperBytes read GetSigningInput; + function VerifySignature: Boolean; overload; + function VerifySignature(AKey: TJWK; const ACompactToken: TJOSEBytes): Boolean; overload; + + property Key: TJOSEBytes read FKey; + property Header: TJOSEBytes read GetHeader write SetHeader; + property Payload: TJOSEBytes read GetPayload write SetPayload; + property Signature: TJOSEBytes read GetSignature write SetSignature; + property SigningInput: TJOSEBytes read GetSigningInput; end; implementation uses - Types, - StrUtils, + System.Types, + System.StrUtils, JOSE.Encoding.Base64, - JOSE.Hashing.HMAC; + JOSE.Hashing.HMAC, + JOSE.Core.JWA.Factory; constructor TJWS.Create(AToken: TJWT); var @@ -83,40 +94,57 @@ constructor TJWS.Create(AToken: TJWT); inherited Create(AToken); for LIndex := 0 to COMPACT_PARTS - 1 do - FParts.Add(TSuperBytes.Empty); + FParts.Add(TJOSEBytes.Empty); end; -function TJWS.GetCompactToken: TSuperBytes; +function TJWS.GetAlgorithm(AAlgId: TJOSEAlgorithmId): IJOSESigningAlgorithm; +var + LAlgId: string; +begin + LAlgId := FToken.Header.Algorithm; + + if LAlgId.IsEmpty then + raise EJOSEException.CreateFmt('Signature algorithm header (%s) not set.', + [THeaderNames.ALGORITHM]); + + Result := TJOSEAlgorithmRegistryFactory.Instance + .SigningAlgorithmRegistry.GetAlgorithm(LAlgId); + if Result = nil then + raise EJOSEException.CreateFmt('Signing algorithm (%s) is not supported.', + [LAlgId]); +end; + +function TJWS.GetCompactToken: TJOSEBytes; begin Result := Header + PART_SEPARATOR + Payload + PART_SEPARATOR + Signature; end; -function TJWS.GetHeader: TSuperBytes; +function TJWS.GetHeader: TJOSEBytes; begin Result := FParts[0] end; -function TJWS.GetPayload: TSuperBytes; +function TJWS.GetPayload: TJOSEBytes; begin Result := FParts[1]; end; -function TJWS.GetSignature: TSuperBytes; +function TJWS.GetSignature: TJOSEBytes; begin Result := FParts[2]; end; -function TJWS.GetSigningInput: TSuperBytes; +function TJWS.GetSigningInput: TJOSEBytes; begin Result := Header + PART_SEPARATOR + Payload; end; -procedure TJWS.SetCompactToken(const Value: TSuperBytes); +procedure TJWS.SetCompactToken(const Value: TJOSEBytes); var LRes: TStringDynArray; begin LRes := SplitString(Value, PART_SEPARATOR); - if Length(LRes) = 3 then + if Length(LRes) = COMPACT_PARTS then begin FParts[0] := LRes[0]; FParts[1] := LRes[1]; @@ -129,76 +157,83 @@ procedure TJWS.SetCompactToken(const Value: TSuperBytes); raise EJOSEException.CreateFmt('A JWS Compact Serialization must have %d parts', [COMPACT_PARTS]); end; -procedure TJWS.SetHeader(const Value: TSuperBytes); +procedure TJWS.SetHeader(const Value: TJOSEBytes); begin FParts[0] := Value; end; -procedure TJWS.SetPayload(const Value: TSuperBytes); +procedure TJWS.SetKey(const AKey: TBytes); +begin + FKey := AKey; +end; + +procedure TJWS.SetKey(const AKey: TJOSEBytes); +begin + FKey := AKey; +end; + +procedure TJWS.SetKey(const AKey: TJWK); +begin + FKey := AKey.Key; +end; + +procedure TJWS.SetPayload(const Value: TJOSEBytes); begin FParts[1] := Value; end; -procedure TJWS.SetSignature(const Value: TSuperBytes); +procedure TJWS.SetSignature(const Value: TJOSEBytes); begin FParts[2] := Value; end; -function TJWS.Sign(AKey: TJWK; AAlg: TJWAEnum): TSuperBytes; -var - LSign: TSuperBytes; +function TJWS.Sign(AKey: TJWK; AAlgId: TJOSEAlgorithmId): TJOSEBytes; begin - Empty; + SetKey(AKey); + SetHeaderAlgorithm(AAlgId); + + Result := Sign(); +end; - {$ifdef DelphiXE8_UP} - FToken.Header.Algorithm := AAlg.AsString; - {$else} - FToken.Header.Algorithm := TJWAEnumHelper.AsString(AAlg); - {$endif} +function TJWS.Sign: TJOSEBytes; +var + LAlgId: TJOSEAlgorithmId; + LAlg: IJOSESigningAlgorithm; +begin + LAlgId.AsString := FToken.Header.Algorithm; + LAlg := GetAlgorithm(LAlgId); - Header := TBase64.URLEncode(FToken.Header.JSON.ToString); - Payload := TBase64.URLEncode(FToken.Claims.JSON.ToString); + if not FSkipKeyValidation then + LAlg.ValidateSigningKey(FKey); - case AAlg of - None: LSign.Clear; - HS256: LSign := THMAC.Sign(SigningInput, AKey.Key, SHA256); - HS384: LSign := THMAC.Sign(SigningInput, AKey.Key, SHA384); - HS512: LSign := THMAC.Sign(SigningInput, AKey.Key, SHA512); - else - raise EJOSEException.Create('Signing algorithm not supported'); - end; - Signature := TBase64.URLEncode(LSign.AsBytes); + Header := TBase64.URLEncode(ToJSON(FToken.Header.JSON)); + Payload := TBase64.URLEncode(ToJSON(FToken.Claims.JSON)); + Signature := LAlg.Sign(FKey, SigningInput); Result := Signature; end; -procedure TJWS.Verify(AKey: TJWK; const ACompactToken: TSuperBytes); +function TJWS.VerifySignature(AKey: TJWK; const ACompactToken: TJOSEBytes): Boolean; +begin + SetKey(AKey); + SetCompactToken(ACompactToken); + + Result := VerifySignature; +end; + +function TJWS.VerifySignature: Boolean; var - LExpectedSign: TSuperBytes; - LAlg: TJWAEnum; -begin - CompactToken := ACompactToken; - - {$ifdef DelphiXE8_UP} - LAlg.AsString := FToken.Header.Algorithm; - {$else} - LAlg := TJWAEnumHelper.FromString(FToken.Header.Algorithm); - {$endif} - case LAlg of - None : FToken.Verified := AKey.Key.IsEmpty; - HS256: LExpectedSign := THMAC.Sign(SigningInput, AKey.Key, SHA256); - HS384: LExpectedSign := THMAC.Sign(SigningInput, AKey.Key, SHA384); - HS512: LExpectedSign := THMAC.Sign(SigningInput, AKey.Key, SHA512); - else - raise EJOSEException.Create('Signing algorithm not supported'); - end; + LAlgId: TJOSEAlgorithmId; + LAlg: IJOSESigningAlgorithm; +begin + LAlgId.AsString := FToken.Header.Algorithm; + LAlg := GetAlgorithm(LAlgId); - if LAlg <> None then - begin - LExpectedSign := TBase64.URLEncode(LExpectedSign); - if LExpectedSign = Signature then - FToken.Verified := True; - end; + if not FSkipKeyValidation then + LAlg.ValidateVerificationKey(FKey); + + Result := LAlg.VerifySignature(FKey, SigningInput, Signature); + FToken.Verified := Result; end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWT.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWT.pas index c31b20eb..fbecdbd0 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWT.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.JWT.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -31,34 +31,31 @@ interface uses - SysUtils, - StrUtils, - DateUtils, - Rtti, - Generics.Collections, - //JOSE.Types.JSON, - MARS.Core.JSON, + System.SysUtils, + System.StrUtils, + System.DateUtils, + System.Rtti, + System.JSON, + System.Generics.Collections, + JOSE.Types.JSON, JOSE.Types.Bytes, JOSE.Core.Base, JOSE.Core.JWA, JOSE.Core.JWK; type - THeaderNames = class - public const - HEADER_TYPE = 'typ'; - ALGORITHM = 'alg'; - end; - TJWTHeader = class sealed(TJOSEBase) private function GetAlgorithm: string; function GetHeaderType: string; procedure SetAlgorithm(const Value: string); procedure SetHeaderType(Value: string); + function GetKeyID: string; + procedure SetKeyID(const Value: string); public property Algorithm: string read GetAlgorithm write SetAlgorithm; property HeaderType: string read GetHeaderType write SetHeaderType; + property KeyID: string read GetKeyID write SetKeyID; end; TReservedClaimNames = class @@ -76,6 +73,8 @@ TReservedClaimNames = class TClaimVerifications = set of TClaimVerification; TJWTClaims = class(TJOSEBase) + private + const AUDIENCE_SEPARATOR = ','; private function GetAudience: string; function GetExpiration: TDateTime; @@ -91,18 +90,38 @@ TJWTClaims = class(TJOSEBase) procedure SetJWTId(Value: string); procedure SetNotBefore(Value: TDateTime); procedure SetSubject(Value: string); + + function GetHasAudience: Boolean; + function GetHasExpiration: Boolean; + function GetHasIssuedAt: Boolean; + function GetHasIssuer: Boolean; + function GetHasJWTId: Boolean; + function GetHasNotBefore: Boolean; + function GetHasSubject: Boolean; + + function ClaimExists(const AClaimName: string): Boolean; + function GetAudienceArray: TArray; + procedure SetAudienceArray(const Value: TArray); public -// procedure SetClaimOfType(const AName: string; const AValue: T); + constructor Create; virtual; + procedure SetClaimOfType(const AName: string; const AValue: T); function GenerateJWTId(ANumberOfBytes: Integer = 16): string; - procedure CheckRegisteredClaims(AOptions: TClaimVerifications = []); virtual; property Audience: string read GetAudience write SetAudience; + property AudienceArray: TArray read GetAudienceArray write SetAudienceArray; + property HasAudience: Boolean read GetHasAudience; property Expiration: TDateTime read GetExpiration write SetExpiration; + property HasExpiration: Boolean read GetHasExpiration; property IssuedAt: TDateTime read GetIssuedAt write SetIssuedAt; + property HasIssuedAt: Boolean read GetHasIssuedAt; property Issuer: string read GetIssuer write SetIssuer; + property HasIssuer: Boolean read GetHasIssuer; property JWTId: string read GetJWTId write SetJWTId; + property HasJWTId: Boolean read GetHasJWTId; property NotBefore: TDateTime read GetNotBefore write SetNotBefore; + property HasNotBefore: Boolean read GetHasNotBefore; property Subject: string read GetSubject write SetSubject; + property HasSubject: Boolean read GetHasSubject; end; TJWTClaimsClass = class of TJWTClaims; @@ -114,9 +133,13 @@ TJWT = class FClaims: TJWTClaims; FHeader: TJWTHeader; public - constructor Create(AClaimsClass: TJWTClaimsClass); + constructor Create; overload; + constructor Create(AClaimsClass: TJWTClaimsClass); overload; destructor Destroy; override; + function GetClaimsAs: T; deprecated; + function ClaimsAs: T; + property Header: TJWTHeader read FHeader; property Claims: TJWTClaims read FClaims; property Verified: Boolean read FVerified write FVerified; @@ -127,6 +150,16 @@ implementation uses JOSE.Encoding.Base64; +function TJWT.GetClaimsAs: T; +begin + Result := FClaims as T; +end; + +function TJWT.ClaimsAs: T; +begin + Result := FClaims as T; +end; + constructor TJWT.Create(AClaimsClass: TJWTClaimsClass); begin FHeader := TJWTHeader.Create; @@ -134,6 +167,11 @@ constructor TJWT.Create(AClaimsClass: TJWTClaimsClass); FClaims := AClaimsClass.Create; end; +constructor TJWT.Create; +begin + Create(TJWTClaims); +end; + destructor TJWT.Destroy; begin FHeader.Free; @@ -143,130 +181,230 @@ destructor TJWT.Destroy; { TJWTClaims } -{ procedure TJWTClaims.SetClaimOfType(const AName: string; const AValue: T); begin AddPairOfType(AName, AValue); end; -} -procedure TJWTClaims.CheckRegisteredClaims(AOptions: TClaimVerifications = []); -//var -// LOption: TClaimVerification; + +function TJWTClaims.ClaimExists(const AClaimName: string): Boolean; begin -{ - for LOption in AOptions do - begin - case LOption of - Audience: ; - Expiration: ; - IssuedAt: ; - Issuer: ; - TokenId: ; - NotBefore: ; - Subject: ; - end; - end; -} + Result := TJSONUtils.CheckPair(AClaimName, FJSON); +end; + +constructor TJWTClaims.Create; +begin + inherited Create; end; function TJWTClaims.GenerateJWTId(ANumberOfBytes: Integer): string; var - LID: TSuperBytes; + LID: TJOSEBytes; begin - LID := TSuperBytes.RandomBytes(ANumberOfBytes); + LID := TJOSEBytes.RandomBytes(ANumberOfBytes); Result := TBase64.URLEncode(LID); end; function TJWTClaims.GetAudience: string; +var + LValue, LAudValue: TJSONValue; + LValueArray: TJSONArray; begin - Result := FJSON.ReadStringValue(TReservedClaimNames.AUDIENCE); + Result := ''; + LAudValue := FJSON.GetValue(TReservedClaimNames.AUDIENCE); + if Assigned(LAudValue) and (LAudValue is TJSONArray) then + begin + LValueArray := LAudValue as TJSONArray; + + for LValue in LValueArray do + Result := Result + LValue.Value + AUDIENCE_SEPARATOR; + + Result := Result.TrimRight([AUDIENCE_SEPARATOR]); + end + else + Result := TJSONUtils.GetJSONValue(TReservedClaimNames.AUDIENCE, FJSON).AsString; +end; + +function TJWTClaims.GetAudienceArray: TArray; +begin + Result := Audience.Split([AUDIENCE_SEPARATOR]); end; function TJWTClaims.GetExpiration: TDateTime; begin - Result := FJSON.ReadUnixTimeValue(TReservedClaimNames.EXPIRATION); + Result := TJSONUtils.GetJSONValueAsEpoch(TReservedClaimNames.EXPIRATION, FJSON); +end; + +function TJWTClaims.GetHasAudience: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.AUDIENCE); +end; + +function TJWTClaims.GetHasExpiration: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.EXPIRATION); +end; + +function TJWTClaims.GetHasIssuedAt: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.ISSUED_AT); +end; + +function TJWTClaims.GetHasIssuer: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.ISSUER); +end; + +function TJWTClaims.GetHasJWTId: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.JWT_ID); +end; + +function TJWTClaims.GetHasNotBefore: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.NOT_BEFORE); +end; + +function TJWTClaims.GetHasSubject: Boolean; +begin + Result := ClaimExists(TReservedClaimNames.SUBJECT); end; function TJWTClaims.GetIssuedAt: TDateTime; begin - Result := FJSON.ReadUnixTimeValue(TReservedClaimNames.ISSUED_AT); + Result := TJSONUtils.GetJSONValueAsEpoch(TReservedClaimNames.ISSUED_AT, FJSON); end; function TJWTClaims.GetIssuer: string; begin - Result := FJSON.ReadStringValue(TReservedClaimNames.ISSUER); + Result := TJSONUtils.GetJSONValue(TReservedClaimNames.ISSUER, FJSON).AsString; end; function TJWTClaims.GetJWTId: string; begin - Result := FJSON.ReadStringValue(TReservedClaimNames.JWT_ID); + Result := TJSONUtils.GetJSONValue(TReservedClaimNames.JWT_ID, FJSON).AsString; end; function TJWTClaims.GetNotBefore: TDateTime; begin - Result := FJSON.ReadUnixTimeValue(TReservedClaimNames.NOT_BEFORE); + Result := TJSONUtils.GetJSONValueAsEpoch(TReservedClaimNames.NOT_BEFORE, FJSON); end; function TJWTClaims.GetSubject: string; begin - Result := FJSON.ReadStringValue(TReservedClaimNames.SUBJECT); + Result := TJSONUtils.GetJSONValue(TReservedClaimNames.SUBJECT, FJSON).AsString; end; procedure TJWTClaims.SetAudience(Value: string); +var + LAudienceArray: TArray; + LAudience: string; + LArray: TJSONArray; begin - FJSON.WriteStringValue(TReservedClaimNames.AUDIENCE, Value); + if Value.IsEmpty then + begin + TJSONUtils.RemoveJSONNode(TReservedClaimNames.AUDIENCE, FJSON); + Exit; + end; + + LAudienceArray := Value.Split([AUDIENCE_SEPARATOR]); + + if Length(LAudienceArray) > 1 then + begin + LArray := TJSONArray.Create; + for LAudience in LAudienceArray do + begin + LArray.Add(LAudience); + end; + FJSON.AddPair(TJSONPair.Create(TReservedClaimNames.AUDIENCE, LArray)); + end; + + if (Length(LAudienceArray) = 1) then + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.AUDIENCE, Value, FJSON); +end; + +procedure TJWTClaims.SetAudienceArray(const Value: TArray); +begin + Audience := string.Join(AUDIENCE_SEPARATOR, Value); end; procedure TJWTClaims.SetExpiration(Value: TDateTime); begin - FJSON.WriteUnixTimeValue(TReservedClaimNames.EXPIRATION, Value); + if Value = 0 then + TJSONUtils.RemoveJSONNode(TReservedClaimNames.EXPIRATION, FJSON) + else + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.EXPIRATION, Value, FJSON); end; procedure TJWTClaims.SetIssuedAt(Value: TDateTime); begin - FJSON.WriteUnixTimeValue(TReservedClaimNames.ISSUED_AT, Value); + if Value = 0 then + TJSONUtils.RemoveJSONNode(TReservedClaimNames.ISSUED_AT, FJSON) + else + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.ISSUED_AT, Value, FJSON); end; procedure TJWTClaims.SetIssuer(Value: string); begin - FJSON.WriteStringValue(TReservedClaimNames.ISSUER, Value); + if Value = '' then + TJSONUtils.RemoveJSONNode(TReservedClaimNames.ISSUER, FJSON) + else + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.ISSUER, Value, FJSON); end; procedure TJWTClaims.SetJWTId(Value: string); begin - FJSON.WriteStringValue(TReservedClaimNames.JWT_ID, Value); + if Value = '' then + TJSONUtils.RemoveJSONNode(TReservedClaimNames.JWT_ID, FJSON) + else + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.JWT_ID, Value, FJSON); end; procedure TJWTClaims.SetNotBefore(Value: TDateTime); begin - FJSON.WriteUnixTimeValue(TReservedClaimNames.NOT_BEFORE, Value); + if Value = 0 then + TJSONUtils.RemoveJSONNode(TReservedClaimNames.NOT_BEFORE, FJSON) + else + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.NOT_BEFORE, Value, FJSON); end; procedure TJWTClaims.SetSubject(Value: string); begin - FJSON.WriteStringValue(TReservedClaimNames.SUBJECT, Value); + if Value = '' then + TJSONUtils.RemoveJSONNode(TReservedClaimNames.SUBJECT, FJSON) + else + TJSONUtils.SetJSONValueFrom(TReservedClaimNames.SUBJECT, Value, FJSON); end; { TJWTHeader } function TJWTHeader.GetAlgorithm: string; begin - Result := FJSON.ReadStringValue(THeaderNames.ALGORITHM); + Result := TJSONUtils.GetJSONValue(THeaderNames.ALGORITHM, FJSON).AsString; end; function TJWTHeader.GetHeaderType: string; begin - Result := FJSON.ReadStringValue(THeaderNames.HEADER_TYPE); + Result := TJSONUtils.GetJSONValue(THeaderNames.HEADER_TYPE, FJSON).AsString; +end; + +function TJWTHeader.GetKeyID: string; +begin + Result := TJSONUtils.GetJSONValue(THeaderNames.KEY_ID, FJSON).AsString; end; procedure TJWTHeader.SetAlgorithm(const Value: string); begin - FJSON.WriteStringValue(THeaderNames.ALGORITHM, Value); + TJSONUtils.SetJSONValueFrom(THeaderNames.ALGORITHM, Value, FJSON); end; procedure TJWTHeader.SetHeaderType(Value: string); begin - FJSON.WriteStringValue(THeaderNames.HEADER_TYPE, Value); + TJSONUtils.SetJSONValueFrom(THeaderNames.HEADER_TYPE, Value, FJSON); +end; + +procedure TJWTHeader.SetKeyID(const Value: string); +begin + TJSONUtils.SetJSONValueFrom(THeaderNames.KEY_ID, Value, FJSON); end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Parts.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Parts.pas index 805cbc7c..259fa6b2 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Parts.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Core.Parts.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -31,22 +31,31 @@ interface SysUtils, Generics.Collections, JOSE.Types.Bytes, + JOSE.Core.JWA, JOSE.Core.JWT; type TJOSEParts = class protected - FParts: TList; + FParts: TList; FToken: TJWT; - function GetCompactToken: TSuperBytes; virtual; abstract; - procedure SetCompactToken(const Value: TSuperBytes); virtual; abstract; + FSkipKeyValidation: Boolean; + function GetCompactToken: TJOSEBytes; virtual; abstract; + procedure SetCompactToken(const Value: TJOSEBytes); virtual; abstract; + + function GetHeaderAlgorithm: string; public constructor Create(AToken: TJWT); virtual; destructor Destroy; override; + procedure SetHeaderAlgorithm(const AAlg: string); overload; + procedure SetHeaderAlgorithm(AAlg: TJOSEAlgorithmId); overload; + procedure Clear; procedure Empty; - property CompactToken: TSuperBytes read GetCompactToken write SetCompactToken; + property CompactToken: TJOSEBytes read GetCompactToken write SetCompactToken; + property HeaderAlgorithm: string read GetHeaderAlgorithm; + property SkipKeyValidation: Boolean read FSkipKeyValidation write FSkipKeyValidation; end; implementation @@ -61,7 +70,7 @@ procedure TJOSEParts.Clear; constructor TJOSEParts.Create(AToken: TJWT); begin FToken := AToken; - FParts := TList.Create; + FParts := TList.Create; end; destructor TJOSEParts.Destroy; @@ -75,7 +84,22 @@ procedure TJOSEParts.Empty; LIndex: Integer; begin for LIndex := 0 to FParts.Count - 1 do - FParts[LIndex] := TSuperBytes.Empty; + FParts[LIndex] := TJOSEBytes.Empty; +end; + +function TJOSEParts.GetHeaderAlgorithm: string; +begin + Result := FToken.Header.Algorithm; +end; + +procedure TJOSEParts.SetHeaderAlgorithm(AAlg: TJOSEAlgorithmId); +begin + FToken.Header.Algorithm := AAlg.AsString; +end; + +procedure TJOSEParts.SetHeaderAlgorithm(const AAlg: string); +begin + FToken.Header.Algorithm := AAlg; end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Encoding.Base64.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Encoding.Base64.pas index d536da73..26d165e9 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Encoding.Base64.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Encoding.Base64.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -25,49 +25,161 @@ /// unit JOSE.Encoding.Base64; -{$I ..\..\..\Source\Mars.inc} interface uses - SysUtils, -{$ifdef DelphiXE7_UP} + System.SysUtils, + {$IF CompilerVersion >= 28} System.NetEncoding, -{$else} - IdCoderMIME, IdGlobal, -{$endif} + {$IFEND} JOSE.Types.Bytes; type TBase64 = class - class function Encode(const ASource: TSuperBytes): TSuperBytes; overload; - class function Decode(const ASource: TSuperBytes): TSuperBytes; overload; - class function URLEncode(const ASource: TSuperBytes): TSuperBytes; overload; - class function URLDecode(const ASource: TSuperBytes): TSuperBytes; overload; + class function Encode(const ASource: TJOSEBytes): TJOSEBytes; overload; + class function Decode(const ASource: TJOSEBytes): TJOSEBytes; overload; + class function URLEncode(const ASource: TJOSEBytes): TJOSEBytes; overload; + class function URLDecode(const ASource: TJOSEBytes): TJOSEBytes; overload; end; implementation +{$IF CompilerVersion <= 27} +type + TPacket = packed record + case Integer of + 0: (b0, b1, b2, b3: Byte); + 1: (i: Integer); + 2: (a: array[0..3] of Byte); + end; + +function DecodeBase64(const AInput: string): TBytes; +const + DECODE_TABLE: array[#0..#127] of Integer = ( + Byte('='), 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64 + ); + + function DecodePacket(AInputBuffer: PChar; var ANumChars: Integer): TPacket; + begin + Result.a[0] := + (DECODE_TABLE[AInputBuffer[0]] shl 2) or (DECODE_TABLE[AInputBuffer[1]] shr 4); + ANumChars := 1; + if AInputBuffer[2] <> '=' then + begin + Inc(ANumChars); + Result.a[1] := (DECODE_TABLE[AInputBuffer[1]] shl 4) or (DECODE_TABLE[AInputBuffer[2]] shr 2); + end; + if AInputBuffer[3] <> '=' then + begin + Inc(ANumChars); + Result.a[2] := (DECODE_TABLE[AInputBuffer[2]] shl 6) or DECODE_TABLE[AInputBuffer[3]]; + end; + end; + +var + I, J, K: Integer; + LPacket: TPacket; + LLen: Integer; +begin + SetLength(Result, Length(AInput) div 4 * 3); + LLen := 0; + for I := 1 to Length(AInput) div 4 do + begin + LPacket := DecodePacket(PChar(@AInput[(I - 1) * 4 + 1]), J); + K := 0; + while J > 0 do + begin + Result[LLen] := LPacket.a[K]; + Inc(LLen); + Inc(K); + Dec(J); + end; + end; + SetLength(Result, LLen); +end; + +function EncodeBase64(const AInput: TBytes): string; +const + ENCODE_TABLE: array[0..63] of Char = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + 'abcdefghijklmnopqrstuvwxyz' + + '0123456789+/'; + + procedure EncodePacket(const APacket: TPacket; ANumChars: Integer; AOutBuffer: PChar); + begin + AOutBuffer[0] := ENCODE_TABLE[APacket.a[0] shr 2]; + AOutBuffer[1] := ENCODE_TABLE[((APacket.a[0] shl 4) or (APacket.a[1] shr 4)) and $0000003f]; + + if ANumChars < 2 then + AOutBuffer[2] := '=' + else + AOutBuffer[2] := ENCODE_TABLE[((APacket.a[1] shl 2) or (APacket.a[2] shr 6)) and $0000003f]; + + if ANumChars < 3 then + AOutBuffer[3] := '=' + else + AOutBuffer[3] := ENCODE_TABLE[APacket.a[2] and $0000003f]; + end; + +var + I, K, J: Integer; + LPacket: TPacket; +begin + Result := ''; + I := (Length(AInput) div 3) * 4; + if Length(AInput) mod 3 > 0 then + Inc(I, 4); + SetLength(Result, I); + J := 1; + for I := 1 to Length(AInput) div 3 do + begin + LPacket.i := 0; + LPacket.a[0] := AInput[(I - 1) * 3]; + LPacket.a[1] := AInput[(I - 1) * 3 + 1]; + LPacket.a[2] := AInput[(I - 1) * 3 + 2]; + EncodePacket(LPacket, 3, PChar(@Result[J])); + Inc(J, 4); + end; + K := 0; + LPacket.i := 0; + for I := Length(AInput) - (Length(AInput) mod 3) + 1 to Length(AInput) do + begin + LPacket.a[K] := Byte(AInput[I - 1]); + Inc(K); + if I = Length(AInput) then + EncodePacket(LPacket, Length(AInput) mod 3, PChar(@Result[J])); + end; +end; +{$IFEND} + { TBase64 } -class function TBase64.Decode(const ASource: TSuperBytes): TSuperBytes; +class function TBase64.Decode(const ASource: TJOSEBytes): TJOSEBytes; begin -{$ifdef DelphiXE7_UP} + {$IF CompilerVersion >= 28} Result := TNetEncoding.Base64.Decode(ASource.AsBytes); -{$else} - Result := TBytes(TIdDecoderMIME.DecodeBytes(ASource.AsString)); -{$endif} + {$ELSE} + Result := DecodeBase64(ASource.AsString); + {$IFEND} end; -class function TBase64.Encode(const ASource: TSuperBytes): TSuperBytes; +class function TBase64.Encode(const ASource: TJOSEBytes): TJOSEBytes; begin -{$ifdef DelphiXE7_UP} + {$IF CompilerVersion >= 28} Result := TNetEncoding.Base64.Encode(ASource.AsBytes); -{$else} - Result := TIdEncoderMIME.EncodeBytes(TIdBytes(ASource.AsBytes)); -{$endif} + {$ELSE} + Result := EncodeBase64(ASource.AsBytes); + {$IFEND} end; -class function TBase64.URLDecode(const ASource: TSuperBytes): TSuperBytes; +class function TBase64.URLDecode(const ASource: TJOSEBytes): TJOSEBytes; var LBase64Str: string; begin @@ -79,7 +191,7 @@ class function TBase64.URLDecode(const ASource: TSuperBytes): TSuperBytes; Result := TBase64.Decode(LBase64Str); end; -class function TBase64.URLEncode(const ASource: TSuperBytes): TSuperBytes; +class function TBase64.URLEncode(const ASource: TJOSEBytes): TJOSEBytes; var LBase64Str: string; begin @@ -97,3 +209,4 @@ class function TBase64.URLEncode(const ASource: TSuperBytes): TSuperBytes; end; end. + diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Hashing.HMAC.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Hashing.HMAC.pas index a0f182e2..f7efc0d5 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Hashing.HMAC.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Hashing.HMAC.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -28,39 +28,60 @@ interface uses - SysUtils, + System.SysUtils, + {$IF CompilerVersion >= 30 } // Delphi 10 Seattle or greater + System.Hash, + {$ELSE} IdGlobal, IdHMAC, IdHMACSHA1, IdSSLOpenSSL, IdHash, + {$IFEND} JOSE.Encoding.Base64; type THMACAlgorithm = (SHA256, SHA384, SHA512); - TIdHMACClass = class of TIdHMAC; + THMACAlgorithmHelper = record helper for THMACAlgorithm + procedure FromString(const AValue: string); + function ToString: string; + end; THMAC = class public class function Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBytes; - end; implementation +{$IF CompilerVersion >= 30 } // Delphi 10 Seattle or greater +class function THMAC.Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBytes; +var + LHashAlg: THashSHA2.TSHA2Version; +begin + LHashAlg := THashSHA2.TSHA2Version.SHA256; + case AAlg of + SHA256: LHashAlg := THashSHA2.TSHA2Version.SHA256; + SHA384: LHashAlg := THashSHA2.TSHA2Version.SHA384; + SHA512: LHashAlg := THashSHA2.TSHA2Version.SHA512; + end; + Result := THashSHA2.GetHMACAsBytes(AInput, AKey, LHashAlg); +end; + +{$ELSE} class function THMAC.Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBytes; var LSigner: TIdHMAC; begin + LSigner := nil; + if not IdSSLOpenSSL.LoadOpenSSLLibrary then - raise Exception.Create('Cannot load OpenSSL library'); + raise Exception.Create('Error Message'); case AAlg of SHA256: LSigner := TIdHMACSHA256.Create; SHA384: LSigner := TIdHMACSHA384.Create; SHA512: LSigner := TIdHMACSHA512.Create; - else - raise Exception.Create('Unknown algorithm'); end; try @@ -70,5 +91,30 @@ class function THMAC.Sign(const AInput, AKey: TBytes; AAlg: THMACAlgorithm): TBy LSigner.Free; end; end; +{$IFEND} + + +{ THMACAlgorithmHelper } + +procedure THMACAlgorithmHelper.FromString(const AValue: string); +begin + if AValue = 'SHA256' then + Self := SHA256 + else if AValue = 'SHA384' then + Self := SHA384 + else if AValue = 'SHA512' then + Self := SHA512 + else + raise Exception.Create('Invalid HMAC algorithm type'); +end; + +function THMACAlgorithmHelper.ToString: string; +begin + case Self of + SHA256: Result := 'SHA256'; + SHA384: Result := 'SHA384'; + SHA512: Result := 'SHA512'; + end; +end; end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Signing.RSA.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Signing.RSA.pas new file mode 100644 index 00000000..651d116f --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Signing.RSA.pas @@ -0,0 +1,391 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +unit JOSE.Signing.RSA; + +interface + +uses + System.SysUtils, + IdGlobal, IdCTypes, IdSSLOpenSSLHeaders, + JOSE.Encoding.Base64; + +type + TRSAAlgorithm = (RS256, RS384, RS512); + TRSAAlgorithmHelper = record helper for TRSAAlgorithm + procedure FromString(const AValue: string); + function ToString: string; + end; + + TRSA = class + private + const PKCS1_SIGNATURE_PUBKEY: RawByteString = '-----BEGIN RSA PUBLIC KEY-----'; + const PKCS1_X509_CERTIFICATE: RawByteString = '-----BEGIN CERTIFICATE-----'; + private + class var _PEM_read_bio_RSA_PUBKEY: function(bp: PBIO; x: PPRSA; cb: ppem_password_cb; u: Pointer): PRSA cdecl; + class var _EVP_MD_CTX_create: function: PEVP_MD_CTX cdecl; + class var _EVP_MD_CTX_destroy: procedure(ctx: PEVP_MD_CTX); cdecl; + private + class procedure LoadOpenSSL; + class function VerifyWithCertificate(const AInput, ASignature, ACertificate: TBytes; AAlg: TRSAAlgorithm): Boolean; + class function VerifyCertificate(const ACertificate: TBytes): Boolean; + public + class function Sign(const AInput, AKey: TBytes; AAlg: TRSAAlgorithm): TBytes; + class function Verify(const AInput, ASignature, AKeyOrCertificate: TBytes; AAlg: TRSAAlgorithm): Boolean; + class function VerifyPublicKey(const AKeyOrCertificate: TBytes): Boolean; + class function VerifyPrivateKey(const AKey: TBytes): Boolean; + end; + +implementation + +{$IFDEF MSWINDOWS} +uses + WinApi.Windows; +{$ENDIF} + +function ArrayToString(const ASource: array of Char): string; +var + LSourcePointer: PChar; + LTarget: string; +begin + LSourcePointer := Addr(ASource); + SetString(LTarget, LSourcePointer, Length(ASource)); + Result := LTarget; +end; + +// Get OpenSSL error and message text +function ERR_GetErrorMessage_OpenSSL: string; +var + LErrMsg: array[0..160] of Char; +begin + ERR_error_string(ERR_get_error, @LErrMsg); + Result := ArrayToString(LErrMsg); +end; + +{ TRSAAlgorithmHelper } + +procedure TRSAAlgorithmHelper.FromString(const AValue: string); +begin + if AValue = 'RS256' then + Self := RS256 + else if AValue = 'RS384' then + Self := RS384 + else if AValue = 'RS512' then + Self := RS512 + else + raise Exception.Create('Invalid RSA algorithm type'); +end; + +function TRSAAlgorithmHelper.ToString: string; +begin + case Self of + RS256: Result := 'RS256'; + RS384: Result := 'RS384'; + RS512: Result := 'RS512'; + end; +end; + +{ TRSA } + +class function TRSA.Sign(const AInput, AKey: TBytes; AAlg: TRSAAlgorithm): TBytes; +var + LPrivKeyBIO: pBIO; + LPrivKey: pEVP_PKEY; + LRsa: pRSA; + + LCtx: PEVP_MD_CTX; + LSha: PEVP_MD; + + LLen: Integer; + LSig: Pointer; +begin + LoadOpenSSL; + + // Load Private RSA Key into RSA object + LPrivKeyBIO := BIO_new(BIO_s_mem); + try + BIO_write(LPrivKeyBIO, @AKey[0], Length(AKey)); + LRsa := PEM_read_bio_RSAPrivateKey(LPrivKeyBIO, nil, nil, nil); + if LRsa = nil then + raise Exception.Create('[RSA] Unable to load private key: ' + ERR_GetErrorMessage_OpenSSL); + finally + BIO_free(LPrivKeyBIO); + end; + + try + // Extract Private key from RSA object + LPrivKey := EVP_PKEY_new(); + if EVP_PKEY_set1_RSA(LPrivKey, LRsa) <> 1 then + raise Exception.Create('[RSA] Unable to extract private key: ' + ERR_GetErrorMessage_OpenSSL); + try + case AAlg of + RS256: LSha := EVP_sha256(); + RS384: LSha := EVP_sha384(); + RS512: LSha := EVP_sha512(); + else + raise Exception.Create('[RSA] Unsupported signing algorithm!'); + end; + + LCtx := _EVP_MD_CTX_create; + try + if EVP_DigestSignInit(LCtx, NIL, LSha, NIL, LPrivKey ) <> 1 then + raise Exception.Create('[RSA] Unable to init context: ' + ERR_GetErrorMessage_OpenSSL); + if EVP_DigestSignUpdate(LCtx, @AInput[0], Length(AInput) ) <> 1 then + raise Exception.Create('[RSA] Unable to update context with payload: ' + ERR_GetErrorMessage_OpenSSL); + + // Get signature, first read signature len + EVP_DigestSignFinal(LCtx, nil, @LLen); + LSig := OPENSSL_malloc(LLen); + try + EVP_DigestSignFinal(LCtx, LSig, @LLen); + SetLength(Result, LLen); + Move(LSig^, Result[0], LLen); + finally + CRYPTO_free(LSig); + end; + finally + _EVP_MD_CTX_destroy(LCtx); + end; + finally + EVP_PKEY_free(LPrivKey); + end; + finally + RSA_Free(LRsa); + end; +end; + +class function TRSA.Verify(const AInput, ASignature, AKeyOrCertificate: TBytes; AAlg: TRSAAlgorithm): Boolean; +var + LPubKeyBIO: pBIO; + LPubKey: pEVP_PKEY; + LRsa: pRSA; + LCtx: PEVP_MD_CTX; + LSha: PEVP_MD; +begin + if CompareMem(@PKCS1_X509_CERTIFICATE[1], @AKeyOrCertificate[0], Length(PKCS1_X509_CERTIFICATE)) then + Result := VerifyWithCertificate(AInput, ASignature, AKeyOrCertificate, AAlg) + else + begin + LoadOpenSSL; + + // Load Public RSA Key into RSA object + LPubKeyBIO := BIO_new(BIO_s_mem); + try + BIO_write(LPubKeyBIO, @AKeyOrCertificate[0], Length(AKeyOrCertificate)); + if CompareMem(@PKCS1_SIGNATURE_PUBKEY[1], @AKeyOrCertificate[0], Length(PKCS1_SIGNATURE_PUBKEY)) then + LRsa := PEM_read_bio_RSAPublicKey(LPubKeyBIO, nil, nil, nil) + else + LRsa := _PEM_read_bio_RSA_PUBKEY(LPubKeyBIO, nil, nil, nil); + if LRsa = nil then + raise Exception.Create('[RSA] Unable to load public key: ' + ERR_GetErrorMessage_OpenSSL); + finally + BIO_free(LPubKeyBIO); + end; + + try + // Extract Public key from RSA object + LPubKey := EVP_PKEY_new(); + try + if EVP_PKEY_set1_RSA(LPubKey, LRsa) <> 1 then + raise Exception.Create('[RSA] Unable to extract public key: ' + ERR_GetErrorMessage_OpenSSL); + + case AAlg of + RS256: LSha := EVP_sha256(); + RS384: LSha := EVP_sha384(); + RS512: LSha := EVP_sha512(); + else + raise Exception.Create('[RSA] Unsupported signing algorithm!'); + end; + + LCtx := _EVP_MD_CTX_create; + try + if EVP_DigestVerifyInit(LCtx, NIL, LSha, NIL, LPubKey) <> 1 then + raise Exception.Create('[RSA] Unable to init context: ' + ERR_GetErrorMessage_OpenSSL); + if EVP_DigestVerifyUpdate(LCtx, @AInput[0], Length(AInput)) <> 1 then + raise Exception.Create('[RSA] Unable to update context with payload: ' + ERR_GetErrorMessage_OpenSSL); + + Result := EVP_DigestVerifyFinal(LCtx, @ASignature[0], Length(ASignature)) = 1; + finally + _EVP_MD_CTX_destroy(LCtx); + end; + finally + EVP_PKEY_free(LPubKey); + end; + finally + RSA_Free(LRsa); + end; + end; +end; + +class function TRSA.VerifyPrivateKey(const AKey: TBytes): Boolean; +var + LPubKeyBIO: pBIO; + LRsa: pRSA; +begin + LoadOpenSSL; + + // Load Public RSA Key + LPubKeyBIO := BIO_new(BIO_s_mem); + try + BIO_write(LPubKeyBIO, @AKey[0], Length(AKey)); + LRsa := PEM_read_bio_RSAPrivateKey(LPubKeyBIO, nil, nil, nil); + Result := (LRsa <> nil); + if Result then + RSA_Free(LRsa); + finally + BIO_free(LPubKeyBIO); + end; +end; + +class function TRSA.VerifyPublicKey(const AKeyOrCertificate: TBytes): Boolean; +var + LPubKeyBIO: pBIO; + LRsa: pRSA; +begin + if CompareMem(@PKCS1_X509_CERTIFICATE[1], @AKeyOrCertificate[0], Length(PKCS1_X509_CERTIFICATE)) then + Result := VerifyCertificate(AKeyOrCertificate) + else + begin + LoadOpenSSL; + + // Load Public RSA Key + LPubKeyBIO := BIO_new(BIO_s_mem); + try + BIO_write(LPubKeyBIO, @AKeyOrCertificate[0], Length(AKeyOrCertificate)); + if CompareMem(@PKCS1_SIGNATURE_PUBKEY[1], @AKeyOrCertificate[0], Length(PKCS1_SIGNATURE_PUBKEY)) then + LRsa := PEM_read_bio_RSAPublicKey(LPubKeyBIO, nil, nil, nil) + else + LRsa := _PEM_read_bio_RSA_PUBKEY(LPubKeyBIO, nil, nil, nil); + Result := (LRsa <> nil); + if Result then + RSA_Free(LRsa); + finally + BIO_free(LPubKeyBIO); + end; + end; +end; + +class function TRSA.VerifyWithCertificate(const AInput, ASignature, ACertificate: TBytes; AAlg: TRSAAlgorithm): Boolean; +var + LCertificateBIO: pBIO; + LX509: pX509; + LPubKey: PEVP_PKEY; + AlgID: integer; + LCtx: PEVP_MD_CTX; + LSha: PEVP_MD; +begin + LoadOpenSSL; + LCertificateBIO := BIO_new(BIO_s_mem); + try + BIO_write(LCertificateBIO, @ACertificate[0], Length(ACertificate)); + if not CompareMem(@PKCS1_X509_CERTIFICATE[1], @ACertificate[0], Length(PKCS1_X509_CERTIFICATE)) then + raise Exception.Create('[CERT] No X509 certificate received'); + + LX509 := PEM_read_bio_X509(LCertificateBIO, nil, nil, nil); + if not Assigned(LX509) then + raise Exception.Create('[CERT] Failure by X509 certificate loading'); + + LPubKey := X509_PUBKEY_get(LX509.cert_info.key); + if not Assigned(LPubKey) then + raise Exception.Create('[CERT] Failure by public key extracting from X509 certificate'); + + AlgID := OBJ_obj2nid(LX509.cert_info.key.algor.algorithm); + if AlgID <> NID_rsaEncryption then + raise Exception.Create('[CERT] Unsupported algorithm type in X509 public key (RSA expected)'); + + case AAlg of + RS256: LSha := EVP_sha256(); + RS384: LSha := EVP_sha384(); + RS512: LSha := EVP_sha512(); + else + raise Exception.Create('[RSA] Unsupported signing algorithm!'); + end; + + LCtx := _EVP_MD_CTX_create; + try + if EVP_DigestVerifyInit(LCtx, nil, LSha, nil, LPubKey) <> 1 then + raise Exception.Create('[RSA] Unable to init context: ' + ERR_GetErrorMessage_OpenSSL); + if EVP_DigestVerifyUpdate(LCtx, @AInput[0], Length(AInput)) <> 1 then + raise Exception.Create('[RSA] Unable to update context with payload: ' + ERR_GetErrorMessage_OpenSSL); + + Result := EVP_DigestVerifyFinal(LCtx, @ASignature[0], Length(ASignature)) = 1; + finally + _EVP_MD_CTX_destroy(LCtx); + end; + finally + BIO_free(LCertificateBIO); + end; +end; + +class function TRSA.VerifyCertificate(const ACertificate: TBytes): Boolean; +var + LCertificateBIO: pBIO; + LX509: pX509; + LPubKey: PEVP_PKEY; + AlgID: integer; +begin + LoadOpenSSL; + LCertificateBIO := BIO_new(BIO_s_mem); + try + BIO_write(LCertificateBIO, @ACertificate[0], Length(ACertificate)); + if not CompareMem(@PKCS1_X509_CERTIFICATE[1], @ACertificate[0], Length(PKCS1_X509_CERTIFICATE)) then + raise Exception.Create('[CERT] No X509 certificate received'); + + LX509 := PEM_read_bio_X509(LCertificateBIO, nil, nil, nil); + LPubKey := X509_PUBKEY_get(LX509.cert_info.key); + AlgID := OBJ_obj2nid(LX509.cert_info.key.algor.algorithm); + Result := Assigned(LX509) and Assigned(LPubKey) and (AlgID = NID_rsaEncryption); + finally + BIO_free(LCertificateBIO); + end; +end; + +class procedure TRSA.LoadOpenSSL; +begin + if not IdSSLOpenSSLHeaders.Load then + raise Exception.Create('[RSA] Unable to load OpenSSL libraries'); + + if @EVP_DigestVerifyInit = nil then + raise Exception.Create('[RSA] Please, use OpenSSL 1.0.0. or newer!'); + + if GetCryptLibHandle <> 0 then + begin + _PEM_read_bio_RSA_PUBKEY := GetProcAddress(GetCryptLibHandle, 'PEM_read_bio_RSA_PUBKEY'); + if @_PEM_read_bio_RSA_PUBKEY = nil then + raise Exception.Create('[RSA] Unable to get proc address for "PEM_read_bio_RSA_PUBKEY"'); + + _EVP_MD_CTX_create := GetProcAddress(GetCryptLibHandle, 'EVP_MD_CTX_create'); + if @_EVP_MD_CTX_create = nil then + raise Exception.Create('[RSA] Unable to get proc address for "EVP_MD_CTX_create"'); + + _EVP_MD_CTX_destroy := GetProcAddress(GetCryptLibHandle, 'EVP_MD_CTX_destroy'); + if @_EVP_MD_CTX_create = nil then + raise Exception.Create('[RSA] Unable to get proc address for "EVP_MD_CTX_destroy"'); + end; +end; + +initialization + TRSA._PEM_read_bio_RSA_PUBKEY := nil; + TRSA._EVP_MD_CTX_create := nil; + TRSA._EVP_MD_CTX_destroy := nil; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Arrays.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Arrays.pas new file mode 100644 index 00000000..7c990977 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Arrays.pas @@ -0,0 +1,273 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// Handy TArray replacement +/// +unit JOSE.Types.Arrays; + +interface + +uses + System.SysUtils, + System.Classes, + System.Generics.Defaults; + +type + TJOSEArray = record + private + FPayload: TArray; + function GetSize: NativeInt; inline; + function GetLast: T; + procedure SetLast(const Value: T); + function GetAsArray: TArray; + procedure SetAsArray(const Value: TArray); + function GetFirst: T; + procedure SetFirst(const Value: T); + procedure SetSize(const Value: NativeInt); + public + class function Create: TJOSEArray; static; + public + class operator Implicit(const AValue: TArray): TJOSEArray; + class operator Implicit(const AValue: TJOSEArray): TArray; + class operator Add(const A: TJOSEArray; const B: T): TJOSEArray; + class operator Add(const A: T; const B: TJOSEArray): TJOSEArray; + class operator Add(const A, B: TJOSEArray): TJOSEArray; + public + procedure Empty; + function Push(AItem: T): Integer; + function Pop: T; + function Shift: T; + procedure Join(const AValue: TJOSEArray); overload; + procedure Join(const AValue: TArray); overload; + function Contains(const AValue: T): Boolean; + function IsEmpty: Boolean; + function ToString: string; + function ToStringPluralForm(const APluralPrefix: string): string; + + property Size: NativeInt read GetSize write SetSize; + property First: T read GetFirst write SetFirst; + property Last: T read GetLast write SetLast; + property AsArray: TArray read GetAsArray write SetAsArray; + end; + +implementation + +uses + System.Rtti; + +{ TJOSEArray } + +function TJOSEArray.Contains(const AValue: T): Boolean; +var + LComparer: IComparer; + LItem: T; +begin + LComparer := TComparer.Default; + Result := False; + for LItem in FPayload do + if LComparer.Compare(LItem, AValue) = 0 then + Exit(True); +end; + +class function TJOSEArray.Create: TJOSEArray; +begin + Result.Empty; +end; + +procedure TJOSEArray.Empty; +begin + SetLength(FPayload, 0); +end; + +function TJOSEArray.GetAsArray: TArray; +begin + Result := FPayload; +end; + +function TJOSEArray.GetFirst: T; +begin + if Size = 0 then + raise Exception.Create('Error Message'); + + Result := FPayload[0]; +end; + +function TJOSEArray.GetLast: T; +begin + if Size = 0 then + raise Exception.Create('Error Message'); + + Result := FPayload[Size - 1]; +end; + +function TJOSEArray.GetSize: NativeInt; +begin + Result := Length(FPayload); +end; + +class operator TJOSEArray.Implicit(const AValue: TArray): TJOSEArray; +begin + Result.FPayload := AValue; +end; + +class operator TJOSEArray.Implicit(const AValue: TJOSEArray): TArray; +begin + Result := AValue.FPayload; +end; + +class operator TJOSEArray.Add(const A: TJOSEArray; const B: T): TJOSEArray; +begin + Result.FPayload := A; + Result.Push(B); +end; + +class operator TJOSEArray.Add(const A: T; const B: TJOSEArray): TJOSEArray; +begin + Result.FPayload := B; + Result.Push(A); +end; + +class operator TJOSEArray.Add(const A, B: TJOSEArray): TJOSEArray; +var + LSizeSource: NativeInt; +begin + Result := A; + if B.Size = 0 then + Exit; + + Result.Join(B); +end; + +function TJOSEArray.IsEmpty: Boolean; +begin + Result := Length(FPayload) = 0; +end; + +procedure TJOSEArray.Join(const AValue: TJOSEArray); +var + LSizeSource, LSizeDest: NativeInt; +begin + LSizeSource := AValue.Size; + if LSizeSource = 0 then + Exit; + + LSizeDest := Size; + Size := LSizeDest + LSizeSource; + Move(AValue.FPayload[0], FPayload[LSizeDest], SizeOf(T) * LSizeSource); +end; + +procedure TJOSEArray.Join(const AValue: TArray); +var + LSizeSource, LSizeDest: NativeInt; +begin + LSizeSource := Length(AValue); + if LSizeSource = 0 then + Exit; + + LSizeDest := Size; + Size := LSizeDest + LSizeSource; + Move(AValue[0], FPayload[LSizeDest], SizeOf(T) * LSizeSource); +end; + +function TJOSEArray.Pop: T; +begin + if Size = 0 then + raise Exception.Create('Error Message'); + + Result := FPayload[Size - 1]; + SetLength(FPayload, Size - 1); +end; + +function TJOSEArray.Push(AItem: T): Integer; +begin + Size := Size + 1; + FPayload[Size - 1] := AItem; + Result := Size; +end; + +procedure TJOSEArray.SetAsArray(const Value: TArray); +begin + FPayload := Value; +end; + +procedure TJOSEArray.SetFirst(const Value: T); +begin + if Size = 0 then + raise Exception.Create('Error Message'); + + FPayload[0] := Value; +end; + +procedure TJOSEArray.SetLast(const Value: T); +begin + if Size = 0 then + raise Exception.Create('Error Message'); + + FPayload[Size - 1] := Value; +end; + +procedure TJOSEArray.SetSize(const Value: NativeInt); +begin + if Value < 0 then + raise Exception.Create('Error Message'); + SetLength(FPayload, Value); +end; + +function TJOSEArray.Shift: T; +var + LIndex: Integer; +begin + if Size = 0 then + raise Exception.Create('Error Message'); + + Result := FPayload[0]; + if Size > 1 then + for LIndex := 1 to Size - 1 do + FPayload[LIndex - 1] := FPayload[LIndex]; + + SetLength(FPayload, Size - 1); +end; + +function TJOSEArray.ToString: string; +var + LValue: TValue; + LItem: T; +begin + Result := ''; + for LItem in FPayload do + begin + LValue := TValue.From(LItem); + Result := Result + LValue.ToString + ','; + end; + Result := Result.TrimRight([',']); +end; + +function TJOSEArray.ToStringPluralForm(const APluralPrefix: string): string; +begin + if Self.Size > 1 then + Result := APluralPrefix + Self.ToString + else + Result := Self.ToString; +end; + +end. diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Bytes.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Bytes.pas index 5ace3f70..06c99b68 100644 --- a/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Bytes.pas +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.Bytes.pas @@ -1,7 +1,7 @@ {******************************************************************************} { } { Delphi JOSE Library } -{ Copyright (c) 2015 Paolo Rossi } +{ Copyright (c) 2015-2017 Paolo Rossi } { https://github.com/paolo-rossi/delphi-jose-jwt } { } {******************************************************************************} @@ -32,29 +32,29 @@ interface Classes; type - TSuperBytes = record + TJOSEBytes = record private FPayload: TBytes; procedure SetAsString(const Value: string); function GetAsString: string; inline; public - class operator Implicit(const AValue: TSuperBytes): string; - class operator Implicit(const AValue: TSuperBytes): TBytes; - class operator Implicit(const AValue: string): TSuperBytes; - class operator Implicit(const AValue: TBytes): TSuperBytes; + class operator Implicit(const AValue: TJOSEBytes): string; + class operator Implicit(const AValue: TJOSEBytes): TBytes; + class operator Implicit(const AValue: string): TJOSEBytes; + class operator Implicit(const AValue: TBytes): TJOSEBytes; - class operator Equal(const A: TSuperBytes; const B: TSuperBytes): Boolean; - class operator Equal(const A: TSuperBytes; const B: string): Boolean; - class operator Equal(const A: TSuperBytes; const B: TBytes): Boolean; + class operator Equal(const A: TJOSEBytes; const B: TJOSEBytes): Boolean; + class operator Equal(const A: TJOSEBytes; const B: string): Boolean; + class operator Equal(const A: TJOSEBytes; const B: TBytes): Boolean; - class operator Add(const A: TSuperBytes; const B: TSuperBytes): TSuperBytes; - class operator Add(const A: TSuperBytes; const B: Byte): TSuperBytes; - class operator Add(const A: TSuperBytes; const B: string): TSuperBytes; - class operator Add(const A: TSuperBytes; const B: TBytes): TSuperBytes; + class operator Add(const A: TJOSEBytes; const B: TJOSEBytes): TJOSEBytes; + class operator Add(const A: TJOSEBytes; const B: Byte): TJOSEBytes; + class operator Add(const A: TJOSEBytes; const B: string): TJOSEBytes; + class operator Add(const A: TJOSEBytes; const B: TBytes): TJOSEBytes; - class function Empty: TSuperBytes; static; - class function RandomBytes(ANumberOfBytes: Integer): TSuperBytes; static; + class function Empty: TJOSEBytes; static; + class function RandomBytes(ANumberOfBytes: Integer): TJOSEBytes; static; function IsEmpty: Boolean; function Size: Integer; @@ -62,7 +62,7 @@ TSuperBytes = record function Contains(const AByte: Byte): Boolean; overload; function Contains(const ABytes: TBytes): Boolean; overload; - function Contains(const ABytes: TSuperBytes): Boolean; overload; + function Contains(const ABytes: TJOSEBytes): Boolean; overload; property AsBytes: TBytes read FPayload write FPayload; property AsString: string read GetAsString write SetAsString; @@ -75,6 +75,7 @@ TBytesUtils = class implementation +{ CompareBytes } function CompareBytes(const A, B: TBytes): Boolean; var @@ -86,31 +87,30 @@ function CompareBytes(const A, B: TBytes): Boolean; Result := CompareMem(Pointer(A), Pointer(B), LLen); end; +{ TJOSEBytes } -{ TSuperBytes } - -class operator TSuperBytes.Add(const A: TSuperBytes; const B: Byte): TSuperBytes; +class operator TJOSEBytes.Add(const A: TJOSEBytes; const B: Byte): TJOSEBytes; begin SetLength(Result.FPayload, A.Size + 1); Move(A.FPayload[0], Result.FPayload[0], A.Size); Result.FPayload[Result.Size-1] := B; end; -class operator TSuperBytes.Add(const A, B: TSuperBytes): TSuperBytes; +class operator TJOSEBytes.Add(const A, B: TJOSEBytes): TJOSEBytes; begin SetLength(Result.FPayload, A.Size + B.Size); Move(A.FPayload[0], Result.FPayload[0], A.Size); Move(B.FPayload[0], Result.FPayload[A.Size], B.Size); end; -class operator TSuperBytes.Add(const A: TSuperBytes; const B: TBytes): TSuperBytes; +class operator TJOSEBytes.Add(const A: TJOSEBytes; const B: TBytes): TJOSEBytes; begin SetLength(Result.FPayload, A.Size + Length(B)); Move(A.FPayload[0], Result.FPayload[0], A.Size); Move(B[0], Result.FPayload[A.Size], Length(B)); end; -class operator TSuperBytes.Add(const A: TSuperBytes; const B: string): TSuperBytes; +class operator TJOSEBytes.Add(const A: TJOSEBytes; const B: string): TJOSEBytes; var LB: TBytes; begin @@ -121,72 +121,72 @@ function CompareBytes(const A, B: TBytes): Boolean; Move(LB[0], Result.FPayload[A.Size], Length(LB)); end; -procedure TSuperBytes.Clear; +procedure TJOSEBytes.Clear; begin SetLength(FPayload, 0); end; -function TSuperBytes.Contains(const AByte: Byte): Boolean; +function TJOSEBytes.Contains(const AByte: Byte): Boolean; begin Result := False; end; -function TSuperBytes.Contains(const ABytes: TBytes): Boolean; +function TJOSEBytes.Contains(const ABytes: TBytes): Boolean; begin Result := False; end; -function TSuperBytes.Contains(const ABytes: TSuperBytes): Boolean; +function TJOSEBytes.Contains(const ABytes: TJOSEBytes): Boolean; begin Result := False; end; -class function TSuperBytes.Empty: TSuperBytes; +class function TJOSEBytes.Empty: TJOSEBytes; begin SetLength(Result.FPayload, 0); end; -class operator TSuperBytes.Equal(const A: TSuperBytes; const B: string): Boolean; +class operator TJOSEBytes.Equal(const A: TJOSEBytes; const B: string): Boolean; begin Result := CompareBytes(A.FPayload, TEncoding.UTF8.GetBytes(B)); end; -class operator TSuperBytes.Equal(const A: TSuperBytes; const B: TBytes): Boolean; +class operator TJOSEBytes.Equal(const A: TJOSEBytes; const B: TBytes): Boolean; begin Result := CompareBytes(A.FPayload, B); end; -class operator TSuperBytes.Equal(const A: TSuperBytes; const B: TSuperBytes): Boolean; +class operator TJOSEBytes.Equal(const A: TJOSEBytes; const B: TJOSEBytes): Boolean; begin Result := CompareBytes(A.FPayload, B.FPayload); end; -function TSuperBytes.GetAsString: string; +function TJOSEBytes.GetAsString: string; begin Result := TEncoding.UTF8.GetString(FPayload); end; -class operator TSuperBytes.Implicit(const AValue: TSuperBytes): TBytes; +class operator TJOSEBytes.Implicit(const AValue: TJOSEBytes): TBytes; begin Result := AValue.AsBytes; end; -class operator TSuperBytes.Implicit(const AValue: TSuperBytes): string; +class operator TJOSEBytes.Implicit(const AValue: TJOSEBytes): string; begin Result := TEncoding.UTF8.GetString(AValue.FPayload); end; -class operator TSuperBytes.Implicit(const AValue: TBytes): TSuperBytes; +class operator TJOSEBytes.Implicit(const AValue: TBytes): TJOSEBytes; begin Result.FPayload := AValue; end; -function TSuperBytes.IsEmpty: Boolean; +function TJOSEBytes.IsEmpty: Boolean; begin Result := Size = 0; end; -class function TSuperBytes.RandomBytes(ANumberOfBytes: Integer): TSuperBytes; +class function TJOSEBytes.RandomBytes(ANumberOfBytes: Integer): TJOSEBytes; var LIndex: Integer; begin @@ -195,17 +195,17 @@ class function TSuperBytes.RandomBytes(ANumberOfBytes: Integer): TSuperBytes; Result.FPayload[LIndex] := Random(255); end; -function TSuperBytes.Size: Integer; +function TJOSEBytes.Size: Integer; begin Result := Length(FPayload); end; -class operator TSuperBytes.Implicit(const AValue: string): TSuperBytes; +class operator TJOSEBytes.Implicit(const AValue: string): TJOSEBytes; begin Result.FPayload := TEncoding.UTF8.GetBytes(AValue); end; -procedure TSuperBytes.SetAsString(const Value: string); +procedure TJOSEBytes.SetAsString(const Value: string); begin FPayload := TEncoding.UTF8.GetBytes(Value); end; diff --git a/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.JSON.pas b/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.JSON.pas new file mode 100644 index 00000000..cb7d1091 --- /dev/null +++ b/ThirdParty/delphi-jose-jwt/Source/JOSE.Types.JSON.pas @@ -0,0 +1,251 @@ +{******************************************************************************} +{ } +{ Delphi JOSE Library } +{ Copyright (c) 2015-2017 Paolo Rossi } +{ https://github.com/paolo-rossi/delphi-jose-jwt } +{ } +{******************************************************************************} +{ } +{ Licensed under the Apache License, Version 2.0 (the "License"); } +{ you may not use this file except in compliance with the License. } +{ You may obtain a copy of the License at } +{ } +{ http://www.apache.org/licenses/LICENSE-2.0 } +{ } +{ Unless required by applicable law or agreed to in writing, software } +{ distributed under the License is distributed on an "AS IS" BASIS, } +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } +{ See the License for the specific language governing permissions and } +{ limitations under the License. } +{ } +{******************************************************************************} + +/// +/// Utility unit to deal with the JSON Delphi classes +/// +unit JOSE.Types.JSON; + +interface + +uses + System.SysUtils, + System.StrUtils, + System.DateUtils, + System.Rtti, + System.JSON; + +type + EJSONConversionException = class(Exception); + + TJSONAncestor = System.JSON.TJSONAncestor; + TJSONPair = System.JSON.TJSONPair; + TJSONValue = System.JSON.TJSONValue; + TJSONTrue = System.JSON.TJSONTrue; + TJSONString = System.JSON.TJSONString; + TJSONNumber = System.JSON.TJSONNumber; + TJSONObject = System.JSON.TJSONObject; + TJSONNull = System.JSON.TJSONNull; + TJSONFalse = System.JSON.TJSONFalse; + TJSONArray = System.JSON.TJSONArray; + + TJSONUtils = class + private + class procedure SetJSONValue(const AName: string; const AValue: TValue; AJSON: TJSONObject); overload; + public + class function ToJSON(AJSONValue: TJSONValue): string; static; + class function CheckPair(const AName: string; AJSON: TJSONObject): Boolean; + class function GetJSONValueInt(const AName: string; AJSON: TJSONObject): TValue; + class function GetJSONValueInt64(const AName: string; AJSON: TJSONObject): TValue; + class function GetJSONValueDouble(const AName: string; AJSON: TJSONObject): TValue; + class function GetJSONValue(const AName: string; AJSON: TJSONObject): TValue; + class function GetJSONValueAsDate(const AName: string; AJSON: TJSONObject): TDateTime; + class function GetJSONValueAsEpoch(const AName: string; AJSON: TJSONObject): TDateTime; + + class procedure SetJSONValueFrom(const AName: string; const AValue: T; AJSON: TJSONObject); + class procedure RemoveJSONNode(const AName: string; AJSON: TJSONObject); + end; + +implementation + +uses + System.TypInfo; + +{ TJSONUtils } + +class function TJSONUtils.CheckPair(const AName: string; AJSON: TJSONObject): Boolean; +begin + Result := Assigned(AJSON.GetValue(AName)); +end; + +class function TJSONUtils.GetJSONValue(const AName: string; AJSON: TJSONObject): TValue; +var + LJSONValue: TJSONValue; +begin + LJSONValue := AJSON.GetValue(AName); + + if not Assigned(LJSONValue) then + Result := TValue.Empty + else if LJSONValue is TJSONTrue then + Result := True + else if LJSONValue is TJSONFalse then + Result := False + else if LJSONValue is TJSONNumber then + Result := TJSONNumber(LJSONValue).AsDouble + else + Result := LJSONValue.Value; +end; + +class function TJSONUtils.GetJSONValueAsDate(const AName: string; AJSON: TJSONObject): TDateTime; +var + LJSONValue: string; +begin + LJSONValue := TJSONUtils.GetJSONValue(AName, AJSON).AsString; + if LJSONValue = '' then + Result := 0 + else + Result := ISO8601ToDate(LJSONValue) +end; + +class function TJSONUtils.GetJSONValueAsEpoch(const AName: string; AJSON: TJSONObject): TDateTime; +var + LJSONValue: Int64; +begin + LJSONValue := TJSONUtils.GetJSONValueInt64(AName, AJSON).AsInt64; + if LJSONValue = 0 then + Result := 0 + else + Result := UnixToDateTime(LJSONValue, False) +end; + +class function TJSONUtils.GetJSONValueDouble(const AName: string; AJSON: TJSONObject): TValue; +var + LJSONValue: TJSONValue; +begin + LJSONValue := AJSON.GetValue(AName); + + if not Assigned(LJSONValue) then + Result := TValue.Empty + else if LJSONValue is TJSONNumber then + Result := TJSONNumber(LJSONValue).AsDouble + else + raise EJSONConversionException.Create('JSON Incompatible type. Expected Double'); +end; + +class function TJSONUtils.GetJSONValueInt(const AName: string; AJSON: TJSONObject): TValue; +var + LJSONValue: TJSONValue; +begin + LJSONValue := AJSON.GetValue(AName); + + if not Assigned(LJSONValue) then + Result := TValue.Empty + else if LJSONValue is TJSONNumber then + Result := TJSONNumber(LJSONValue).AsInt + else + raise EJSONConversionException.Create('JSON Incompatible type. Expected Integer'); +end; + +class function TJSONUtils.GetJSONValueInt64(const AName: string; AJSON: TJSONObject): TValue; +var + LJSONValue: TJSONValue; +begin + LJSONValue := AJSON.GetValue(AName); + + if not Assigned(LJSONValue) then + Result := TValue.Empty + else if LJSONValue is TJSONNumber then + Result := TJSONNumber(LJSONValue).AsInt64 + else + raise EJSONConversionException.Create('JSON Incompatible type. Expected Int64'); +end; + +class procedure TJSONUtils.RemoveJSONNode(const AName: string; AJSON: TJSONObject); +var + LPair: TJSONPair; +begin + LPair := AJSON.RemovePair(AName); + if Assigned(LPair) then + LPair.Free; +end; + +class procedure TJSONUtils.SetJSONValueFrom(const AName: string; const AValue: T; AJSON: TJSONObject); +begin + SetJSONValue(AName, TValue.From(AValue), AJSON); +end; + +class function TJSONUtils.ToJSON(AJSONValue: TJSONValue): string; +var + LBytes: TBytes; +begin + SetLength(LBytes, AJSONValue.ToString.Length * 6); + SetLength(LBytes, AJSONValue.ToBytes(LBytes, 0)); + Result := TEncoding.Default.GetString(LBytes); +end; + +class procedure TJSONUtils.SetJSONValue(const AName: string; const AValue: TValue; AJSON: TJSONObject); +var + LPair: TJSONPair; + LValue: TJSONValue; +begin + LValue := nil; + + case AValue.Kind of + tkChar, + tkString, + tkWChar, + tkLString, + tkWString, + tkUString: + begin + LValue := TJSONString.Create(AValue.AsType); + end; + + tkEnumeration: + begin + if AValue.TypeInfo^.NameFld.ToString = 'Boolean' then + begin + if AValue.AsType then + LValue := TJSONTrue.Create + else + LValue := TJSONFalse.Create; + end; + end; + + tkInteger, + tkInt64, + tkFloat: + begin + if SameText(AValue.TypeInfo^.NameFld.ToString, 'TDateTime') or + SameText(AValue.TypeInfo^.NameFld.ToString, 'TDate') or + SameText(AValue.TypeInfo^.NameFld.ToString, 'TTime') then + LValue := TJSONNumber.Create(DateTimeToUnix(AValue.AsType, False)) + else + if AValue.Kind = tkFloat then + LValue := TJSONNumber.Create(AValue.AsType) + else + if AValue.Kind = tkInt64 then + LValue := TJSONNumber.Create(AValue.AsType) + else + LValue := TJSONNumber.Create(AValue.AsType) + end; + end; + + if not Assigned(LValue) then + Exit; + + LPair := AJSON.Get(AName); + if Assigned(LPair) then + begin + // Replace the JSON Value (the previous is freed by the TJSONPair object) + LPair.JsonValue := LValue; + end + else + begin + LPair := TJSONPair.Create(AName, LValue); + AJSON.AddPair(LPair); + end; + +end; + +end. + diff --git a/ThirdParty/mORMot/Source/SynCommons.pas b/ThirdParty/mORMot/Source/SynCommons.pas index 7ac057de..fb05ed9f 100644 --- a/ThirdParty/mORMot/Source/SynCommons.pas +++ b/ThirdParty/mORMot/Source/SynCommons.pas @@ -6,7 +6,7 @@ (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2018 Arnaud Bouchez + Synopse framework. Copyright (C) 2019 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,10 +25,11 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2018 + Portions created by the Initial Developer are Copyright (C) 2019 the Initial Developer. All Rights Reserved. Contributor(s): + - Alan Chate - Aleksandr (sha) - Alfred Glaenzer (alf) - ASiwon @@ -74,13 +75,9 @@ interface uses -{$ifndef LVCL} -{$ifndef FPC} -{$ifndef HASFASTMM4} +{$ifdef WITH_FASTMM4STATS} FastMM4, {$endif} -{$endif} -{$endif} {$ifdef MSWINDOWS} Windows, Messages, @@ -88,26 +85,26 @@ interface Registry, {$endif} {$else MSWINDOWS} -{$ifdef KYLIX3} - Types, - LibC, - SynKylix, -{$endif} -{$ifdef FPC} - BaseUnix, -{$endif} + {$ifdef KYLIX3} + Types, + LibC, + SynKylix, + {$endif KYLIX3} + {$ifdef FPC} + BaseUnix, + {$endif FPC} {$endif MSWINDOWS} Classes, {$ifndef LVCL} SyncObjs, // for TEvent and TCriticalSection Contnrs, // for TObjectList -{$ifdef HASINLINE} - Types, -{$endif} -{$endif} + {$ifdef HASINLINE} + Types, + {$endif HASINLINE} +{$endif LVCL} {$ifndef NOVARIANTS} Variants, -{$endif} +{$endif NOVARIANTS} SynLZ, // needed for TSynMapFile .mab format SysUtils; @@ -121,9 +118,15 @@ interface /// a text including the version and the main active conditional options // - usefull for low-level debugging purpose SYNOPSE_FRAMEWORK_FULLVERSION = SYNOPSE_FRAMEWORK_VERSION - {$ifndef FPC} + {$ifdef FPC} + {$ifdef FPC_FASTMM4}+' FMM4'{$else} + {$ifdef FPC_SYNTBB}+' TBB'{$else} + {$ifdef FPC_SYNJEMALLOC}+' JM'{$else} + {$ifdef FPC_SYNCMEM}+' GM'{$else} + {$ifdef FPC_CMEM}+' CM'{$endif}{$endif}{$endif}{$endif}{$endif} + {$else} {$ifdef LVCL}+' LVCL'{$else} - {$ifdef ENHANCEDRTL}+' ERTL'{$endif}{$endif} + {$ifdef ENHANCEDRTL}+' ERTL'{$endif}{$endif} {$ifdef DOPATCHTRTL}+' PRTL'{$endif} {$ifdef FullDebugMode}+' FDM'{$endif} {$endif FPC}; @@ -170,17 +173,17 @@ interface PtrUInt = NativeUInt; {$else} /// a CPU-dependent signed integer type cast of a pointer / register - // - used for 64 bits compatibility, native under Free Pascal Compiler + // - used for 64-bit compatibility, native under Free Pascal Compiler PtrInt = integer; /// a CPU-dependent unsigned integer type cast of a pointer / register - // - used for 64 bits compatibility, native under Free Pascal Compiler + // - used for 64-bit compatibility, native under Free Pascal Compiler PtrUInt = cardinal; {$endif} /// a CPU-dependent unsigned integer type cast of a pointer of pointer - // - used for 64 bits compatibility, native under Free Pascal Compiler + // - used for 64-bit compatibility, native under Free Pascal Compiler PPtrUInt = ^PtrUInt; /// a CPU-dependent signed integer type cast of a pointer of pointer - // - used for 64 bits compatibility, native under Free Pascal Compiler + // - used for 64-bit compatibility, native under Free Pascal Compiler PPtrInt = ^PtrInt; /// unsigned Int64 doesn't exist under older Delphi, but is defined in FPC @@ -724,9 +727,10 @@ TSynAnsiUTF8 = class(TSynAnsiConvert) procedure UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal; var result: RawByteString); override; /// convert any UTF-8 encoded String into Ansi Text - // - internaly calls UTF8BufferToAnsi virtual method + // - directly assign the input as result, since no conversion is needed function UTF8ToAnsi(const UTF8: RawUTF8): RawByteString; override; /// convert any Ansi Text into an UTF-8 encoded String + // - directly assign the input as result, since no conversion is needed function AnsiToUTF8(const AnsiText: RawByteString): RawUTF8; override; /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded string function AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardinal): RawUTF8; override; @@ -771,7 +775,10 @@ TSynAnsiUTF16 = class(TSynAnsiConvert) // - could be used e.g. to make a temporary copy when JSON is parsed in-place // - call one of the Init() overloaded methods, then Done to release its memory // - will avoid temporary memory allocation via the heap for up to 4KB of data - {$ifdef UNICODE}TSynTempBuffer = record{$else}TSynTempBuffer = object{$endif} + // - all Init() methods will allocate 16 more bytes, for a trailing #0 and + // to ensure our fast JSON parsing won't trigger any GPF (since it may read + // up to 4 bytes ahead via its PInteger() trick) or any SSE4.2 function + {$ifdef FPC_OR_UNICODE}TSynTempBuffer = record{$else}TSynTempBuffer = object{$endif} public /// the text/binary length, in bytes, excluding the trailing #0 len: integer; @@ -786,7 +793,7 @@ {$ifdef UNICODE}TSynTempBuffer = record{$else}TSynTempBuffer = object{$endif} /// initialize a temporary copy of the supplied text buffer procedure Init(Source: pointer; SourceLen: integer); overload; /// initialize a new temporary buffer of a given number of bytes - function Init(SourceLen: integer): pointer; overload; + function Init(SourceLen: integer): pointer; overload; {$ifdef HASINLINE}inline;{$endif} /// initialize the buffer returning the internal buffer size (4095 bytes) // - could be used e.g. for an API call, first trying with plain temp.Init // and using temp.buf and temp.len safely in the call, only calling @@ -795,7 +802,7 @@ {$ifdef UNICODE}TSynTempBuffer = record{$else}TSynTempBuffer = object{$endif} function Init: integer; overload; {$ifdef HASINLINE}inline;{$endif} /// initialize a new temporary buffer of a given number of random bytes // - will fill the buffer via FillRandom() calls - function InitRandom(RandomLen: integer): pointer; + function InitRandom(RandomLen: integer; forcegsl: boolean=true): pointer; /// initialize a new temporary buffer filled with integer increasing values function InitIncreasing(Count: integer; Start: integer=0): PIntegerArray; /// initialize a new temporary buffer of a given number of zero bytes @@ -811,8 +818,8 @@ {$ifdef UNICODE}TSynTempBuffer = record{$else}TSynTempBuffer = object{$endif} /// implements a stack-based writable storage of binary content // - memory allocation is performed via a TSynTempBuffer - {$ifdef UNICODE}TSynTempWriter = record{$else}TSynTempWriter = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TSynTempWriter = record private + {$else}TSynTempWriter = object protected{$endif} tmp: TSynTempBuffer; public /// the current writable position in tmp.buf @@ -950,6 +957,11 @@ {$ifdef UNICODE}TSynTempWriter = record{$else}TSynTempWriter = object{$endif} /// can be used to avoid a memory allocation for res := 'null' NULL_STR_VAR: RawUTF8; +/// compute the new capacity when expanding an array of items +// - handle small, medium and large sizes properly to reduce memory usage and +// maximize performance +function NextGrow(capacity: integer): integer; + /// equivalence to SetString(s,nil,len) function // - faster especially under FPC procedure FastSetString(var s: RawUTF8; p: pointer; len: PtrInt); @@ -958,6 +970,11 @@ procedure FastSetString(var s: RawUTF8; p: pointer; len: PtrInt); // - faster especially under FPC procedure FastSetStringCP(var s; p: pointer; len, codepage: PtrInt); +/// initialize a RawByteString, ensuring returned "aligned" pointer is 16-bytes aligned +// - to be used e.g. for proper SSE process +procedure GetMemAligned(var s: RawByteString; p: pointer; len: PtrInt; + out aligned: pointer); + /// equivalence to @UTF8[1] expression to ensure a RawUTF8 variable is unique // - will ensure that the string refcount is 1, and return a pointer to the text // - under FPC, @UTF8[1] does not call UniqueString() as it does with Delphi @@ -1140,7 +1157,16 @@ function UTF8ToWideChar(dest: PWideChar; source: PUTF8Char; function Utf8ToUnicodeLength(source: PUTF8Char): PtrUInt; /// returns TRUE if the supplied buffer has valid UTF-8 encoding -function IsValidUTF8(source: PUTF8Char): Boolean; +// - will stop when the buffer contains #0 +function IsValidUTF8(source: PUTF8Char): Boolean; overload; + +/// returns TRUE if the supplied buffer has valid UTF-8 encoding +// - will also refuse #0 characters within the buffer +function IsValidUTF8(source: PUTF8Char; sourcelen: PtrInt): Boolean; overload; + +/// returns TRUE if the supplied buffer has valid UTF-8 encoding +// - will also refuse #0 characters within the buffer +function IsValidUTF8(const source: RawUTF8): Boolean; overload; /// returns TRUE if the supplied buffer has valid UTF-8 encoding with no #1..#31 // control characters @@ -1221,8 +1247,7 @@ function RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer; /// convert a RawUnicode UTF-16 PWideChar into a UTF-8 buffer // - replace system.UnicodeToUtf8 implementation, which is rather slow // since Delphi 2009+ -// - will append a trailing #0 to the ending PUTF8Char, unless -// ccfNoTrailingZero is set +// - append a trailing #0 to the ending PUTF8Char, unless ccfNoTrailingZero is set // - if ccfReplacementCharacterForUnmatchedSurrogate is set, this function will identify // unmatched surrogate pairs and replace them with EF BF BD / FFFD Unicode // Replacement character - see https://en.wikipedia.org/wiki/Specials_(Unicode_block) @@ -1355,6 +1380,14 @@ function ToUTF8({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} guid: TGUI // - used in mORMot.pas unit e.g. by TDocVariantData.SortByValue TVariantCompare = function(const V1,V2: variant): PtrInt; +/// TVariantCompare-compatible case-sensitive comparison function +// - just a wrapper around SortDynArrayVariantComp(caseInsensitive=false) +function VariantCompare(const V1,V2: variant): PtrInt; + +/// TVariantCompare-compatible case-insensitive comparison function +// - just a wrapper around SortDynArrayVariantComp(caseInsensitive=true) +function VariantCompareI(const V1,V2: variant): PtrInt; + /// convert any Variant into UTF-8 encoded String // - use VariantSaveJSON() instead if you need a conversion to JSON with // custom parameters @@ -1371,7 +1404,8 @@ function ToUTF8(const V: Variant): RawUTF8; overload; // - use VariantSaveJSON() instead if you need a conversion to JSON with // custom parameters // - wasString is set if the V value was a text -// - custom variant types will be stored as JSON +// - empty and null variants will be stored as 'null' text - as expected by JSON +// - custom variant types (e.g. TDocVariant) will be stored as JSON procedure VariantToUTF8(const V: Variant; var result: RawUTF8; var wasString: boolean); overload; @@ -1379,7 +1413,8 @@ procedure VariantToUTF8(const V: Variant; var result: RawUTF8; // - use VariantSaveJSON() instead if you need a conversion to JSON with // custom parameters // - returns TRUE if the V value was a text, FALSE if was not (e.g. a number) -// - custom variant types will be stored as JSON +// - empty and null variants will be stored as 'null' text - as expected by JSON +// - custom variant types (e.g. TDocVariant) will be stored as JSON function VariantToUTF8(const V: Variant; var Text: RawUTF8): boolean; overload; /// convert any date/time Variant into a TDateTime value @@ -1419,7 +1454,7 @@ procedure VariantToInlineValue(const V: Variant; var result: RawUTF8); function VariantToVariantUTF8(const V: Variant): variant; /// faster alternative to Finalize(aVariantDynArray) -// - this function will take in account and optimize the release of a dynamic +// - this function will take account and optimize the release of a dynamic // array of custom variant types values // - for instance, an array of TDocVariant will be optimized for speed procedure VariantDynArrayClear(var Value: TVariantDynArray); @@ -1435,19 +1470,19 @@ function VariantHash(const value: variant; CaseInsensitive: boolean; { note: those VariantToInteger*() functions are expected to be there } -/// convert any numerical Variant into a 32 bit integer +/// convert any numerical Variant into a 32-bit integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return FALSE and won't change the // Value variable content function VariantToInteger(const V: Variant; var Value: integer): boolean; -/// convert any numerical Variant into a 64 bit integer +/// convert any numerical Variant into a 64-bit integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return FALSE and won't change the // Value variable content function VariantToInt64(const V: Variant; var Value: Int64): boolean; -/// convert any numerical Variant into a 64 bit integer +/// convert any numerical Variant into a 64-bit integer // - it will expect true numerical Variant and won't convert any string nor // floating-pointer Variant, which will return the supplied DefaultValue function VariantToInt64Def(const V: Variant; DefaultValue: Int64): Int64; @@ -1613,6 +1648,9 @@ procedure FormatUTF8(const Format: RawUTF8; const Args: array of const; procedure FormatShort(const Format: RawUTF8; const Args: array of const; var result: shortstring); +/// fast Format() function replacement, for UTF-8 content stored in shortstring +function FormatToShort(const Format: RawUTF8; const Args: array of const): shortstring; + /// fast Format() function replacement, tuned for small content // - use the same single token % (and implementation) than FormatUTF8() procedure FormatString(const Format: RawUTF8; const Args: array of const; @@ -1628,6 +1666,7 @@ function FormatString(const Format: RawUTF8; const Args: array of const): string // - such result type would avoid a string allocation on heap, so are highly // recommended e.g. when logging small pieces of information TShort16 = string[16]; + PShort16 = ^TShort16; /// fast Format() function replacement, for UTF-8 content stored in TShort16 // - truncate result if the text size exceeds 16 bytes @@ -1651,7 +1690,8 @@ function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const; /// read and store text into values[] according to fmt specifiers // - %d as PInteger, %D as PInt64, %u as PCardinal, %U as PQWord, %f as PDouble, // %F as PCurrency, %x as 8 hexa chars to PInteger, %X as 16 hexa chars to PInt64, -// %s as PShortString (UTF-8 encoded) +// %s as PShortString (UTF-8 encoded), %S as PRawUTF8, %L as PRawUTF8 (getting +// all text until the end of the line) // - optionally, specifiers and any whitespace separated identifiers may be // extracted and stored into the ident[] array, e.g. '%dFirstInt %s %DOneInt64' // will store ['dFirstInt','s','DOneInt64'] into ident @@ -1732,18 +1772,18 @@ function VarRecAsChar(const V: TVarRec): integer; // - used in mORMot.pas unit during TSQLTable rows sort and by TSQLQuery TUTF8Compare = function(P1,P2: PUTF8Char): PtrInt; -/// convert the endianness of a given unsigned 32 bit integer into BigEndian +/// convert the endianness of a given unsigned 32-bit integer into BigEndian function bswap32(a: cardinal): cardinal; {$ifdef FPC}inline;{$endif} -/// convert the endianness of a given unsigned 64 bit integer into BigEndian +/// convert the endianness of a given unsigned 64-bit integer into BigEndian function bswap64(const a: QWord): QWord; {$ifdef FPC}inline;{$endif} -/// convert the endianness of an array of unsigned 64 bit integer into BigEndian +/// convert the endianness of an array of unsigned 64-bit integer into BigEndian // - n is required to be > 0 // - warning: on x86, a should be <> b -procedure bswap64array(a,b: PQWordArray; n: integer); +procedure bswap64array(a,b: PQWordArray; n: PtrInt); /// fast concatenation of several AnsiStrings function RawByteStringArrayConcat(const Values: array of RawByteString): RawByteString; @@ -1798,7 +1838,20 @@ function CompareMemFixed(P1, P2: Pointer; Length: PtrInt): Boolean; inline; function CompareMemSmall(P1, P2: Pointer; Length: PtrInt): Boolean; {$ifdef HASINLINE}inline;{$endif} /// convert an IPv4 'x.x.x.x' text into its 32-bit value -function IPToCardinal(const aIP: RawUTF8; out aValue: cardinal): boolean; +// - returns TRUE if the text was a valid IPv4 text, FALSE on parsing error +// - '' or '127.0.0.1' will also return false +function IPToCardinal(const aIP: RawUTF8; out aValue: cardinal): boolean; overload; + +/// convert an IPv4 'x.x.x.x' text into its 32-bit value +// - returns TRUE if the text was a valid IPv4 text, FALSE on parsing error +// - '' or '127.0.0.1' will also return false +function IPToCardinal(P: PUTF8Char; out aValue: cardinal): boolean; overload; + +/// convert an IPv4 'x.x.x.x' text into its 32-bit value, 0 or localhost +// - returns <> 0 value if the text was a valid IPv4 text, 0 on parsing error +// - '' or '127.0.0.1' will also return 0 +function IPToCardinal(const aIP: RawUTF8): cardinal; overload; + {$ifdef HASINLINE}inline;{$endif} /// convert some ASCII-7 text into binary, using Emile Baudot code // - as used in telegraphs, covering a-z 0-9 - ' , ! : ( + ) $ ? @ . / ; charset @@ -1854,13 +1907,13 @@ procedure UInt64ToUtf8(Value: QWord; var result: RawUTF8); /// use our fast RawUTF8 version of IntToStr() // - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009 // - only useful if our Enhanced Runtime (or LVCL) library is not installed -function Int32ToUtf8(Value: integer): RawUTF8; overload; +function Int32ToUtf8(Value: PtrInt): RawUTF8; overload; {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif} /// use our fast RawUTF8 version of IntToStr() // - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009 // - result as var parameter saves a local assignment and a try..finally -procedure Int32ToUTF8(Value: integer; var result: RawUTF8); overload; +procedure Int32ToUTF8(Value: PtrInt; var result: RawUTF8); overload; {$ifdef HASINLINE}inline;{$endif} /// use our fast RawUTF8 version of IntToStr() @@ -1880,11 +1933,11 @@ function ToUTF8(Value: Int64): RawUTF8; overload; {$endif} /// optimized conversion of a cardinal into RawUTF8 -function UInt32ToUtf8(Value: cardinal): RawUTF8; overload; +function UInt32ToUtf8(Value: PtrUInt): RawUTF8; overload; {$ifdef HASINLINE}inline;{$endif} /// optimized conversion of a cardinal into RawUTF8 -procedure UInt32ToUtf8(Value: cardinal; var result: RawUTF8); overload; +procedure UInt32ToUtf8(Value: PtrUInt; var result: RawUTF8); overload; {$ifdef HASINLINE}inline;{$endif} /// faster version than default SysUtils.IntToStr implementation @@ -1909,7 +1962,7 @@ function Curr64ToString(Value: Int64): string; TSynAnsicharSet = set of AnsiChar; /// used to store a set of 8-bit unsigned integers TSynByteSet = set of Byte; - /// used to store a set of 8-bit unsigned integers as 256 bytes + /// used to store a set of 8-bit unsigned integers as 256 booleans TSynByteBoolean = array[byte] of boolean; /// returns the supplied text content, without any control char @@ -1971,6 +2024,29 @@ function SameValue(const A, B: Double; DoublePrec: double = 1E-12): Boolean; // - if you know the precision range of A and B, it's faster to check abs(A-B)QWord(B) is wrong on older versions of Delphi, so you +// should better use this function or SortDynArrayQWord() to properly compare +// two QWord values over CPUX86 +function CompareQWord(A, B: QWord): integer; + {$ifdef HASINLINE}inline;{$endif} + /// compute the sum of values, using a running compensation for lost low-order bits // - a naive "Sum := Sum + Data" will be restricted to 53 bits of resolution, // so will eventually result in an incorrect number @@ -2015,6 +2091,7 @@ procedure ExtendedToStr(Value: TSynExtended; Precision: integer; var result: Raw /// convert a floating-point value to its numerical text equivalency function DoubleToStr(Value: Double): RawUTF8; + {$ifdef HASINLINE}inline;{$endif} /// fast retrieve the position of a given character function PosChar(Str: PUTF8Char; Chr: AnsiChar): PUTF8Char; @@ -2042,19 +2119,21 @@ function PosIU(substr: PUTF8Char; const str: RawUTF8): Integer; // - expect the last available temporary char position in P // - return the last written char position (write in reverse order in P^) // - typical use: -// !function Int32ToUTF8(Value : integer): RawUTF8; -// !var tmp: array[0..15] of AnsiChar; +// !function Int32ToUTF8(Value: PtrInt): RawUTF8; +// !var tmp: array[0..23] of AnsiChar; // ! P: PAnsiChar; // !begin -// ! P := StrInt32(@tmp[15],Value); -// ! SetString(result,P,@tmp[15]-P); +// ! P := StrInt32(@tmp[23],Value); +// ! SetString(result,P,@tmp[23]-P); // !end; -// - not to be called directly: use IntToStr() instead +// - convert the input value as PtrInt, so as Int64 on 64-bit CPUs +// - not to be called directly: use IntToStr() or Int32ToUTF8() instead function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar; /// internal fast unsigned integer val to text conversion // - expect the last available temporary char position in P // - return the last written char position (write in reverse order in P^) +// - convert the input value as PtrUInt, so as QWord on 64-bit CPUs function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar; /// internal fast Int64 val to text conversion @@ -2084,7 +2163,7 @@ procedure AppendBuffersToRawUTF8(var Text: RawUTF8; const Buffers: array of PUTF // you may encounter buffer overflows and random memory errors function AppendRawUTF8ToBuffer(Buffer: PUTF8Char; const Text: RawUTF8): PUTF8Char; -/// fast add text conversion of a 32 bit signed integer value into a given buffer +/// fast add text conversion of a 32-bit signed integer value into a given buffer // - warning: the Buffer should contain enough space to store the text, otherwise // you may encounter buffer overflows and random memory errors function AppendUInt32ToBuffer(Buffer: PUTF8Char; Value: cardinal): PUTF8Char; @@ -2094,24 +2173,14 @@ function AppendUInt32ToBuffer(Buffer: PUTF8Char; Value: cardinal): PUTF8Char; // - pure pascal StrComp() won't access the memory beyond the string, but this // function is defined for compatibility with SSE 4.2 expectations function StrCompFast(Str1, Str2: pointer): PtrInt; + {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif} /// fastest available version of StrComp(), to be used with PUTF8Char/PAnsiChar -// - will use SSE4.2 instructions on supported CPUs - and potentiall read up -// to 15 bytes beyond the string: use StrCompFast() for a safe memory read; -// if you want to disable StrCompSSE42 for your whole project, add in the -// initialization section of one of your units: -// ! StrComp := @StrCompFast; +// - won't use SSE4.2 instructions on supported CPUs by default, which may read +// some bytes beyond the s string, so should be avoided e.g. over memory mapped +// files - call explicitely StrCompSSE42() if you are confident on your input var StrComp: function (Str1, Str2: pointer): PtrInt = StrCompFast; -{$ifndef PUREPASCAL} -/// SSE 4.2 version of StrComp(), to be used with PUTF8Char/PAnsiChar -// - please note that this optimized version may read up to 15 bytes -// beyond the string; this is rarely a problem but it may in principle -// generate a protection violation (e.g. when used over memory mapped files) - -// you can use the slightly slower but safe StrCompFast() function instead -function StrCompSSE42(Str1, Str2: pointer): PtrInt; -{$endif PUREPASCAL} - /// pure pascal version of strspn(), to be used with PUTF8Char/PAnsiChar // - please note that this optimized version may read up to 3 bytes beyond // accept but never after s end, so is safe e.g. over memory mapped files @@ -2124,33 +2193,58 @@ function strspnpas(s,accept: pointer): integer; function strcspnpas(s,reject: pointer): integer; {$ifdef HASINLINE}inline;{$endif} +/// fastest available version of strspn(), to be used with PUTF8Char/PAnsiChar +// - returns size of initial segment of s which appears in accept chars, e.g. +// ! strspn('abcdef','debca')=5 +// - won't use SSE4.2 instructions on supported CPUs by default, which may read +// some bytes beyond the s string, so should be avoided e.g. over memory mapped +// files - call explicitely strspnsse42() if you are confident on your input +var strspn: function (s,accept: pointer): integer = strspnpas; + +/// fastest available version of strcspn(), to be used with PUTF8Char/PAnsiChar +// - returns size of initial segment of s which doesn't appears in reject chars, e.g. +// ! strcspn('1234,6789',',')=4 +// - won't use SSE4.2 instructions on supported CPUs by default, which may read +// some bytes beyond the s string, so should be avoided e.g. over memory mapped +// files - call explicitely strcspnsse42() if you are confident on your input +var strcspn: function (s,reject: pointer): integer = strcspnpas; + +{$ifndef ABSOLUTEPASCAL} {$ifdef CPUINTEL} +{$ifdef HASAESNI} +/// SSE 4.2 version of StrComp(), to be used with PUTF8Char/PAnsiChar +// - please note that this optimized version may read up to 15 bytes +// beyond the string; this is rarely a problem but it may generate protection +// violations, which could trigger fatal SIGABRT or SIGSEGV on Posix system +// - could be used instead of StrComp() when you are confident about your +// Str1/Str2 input buffers, checking if cfSSE42 in CpuFeatures +function StrCompSSE42(Str1, Str2: pointer): PtrInt; + +// - please note that this optimized version may read up to 15 bytes +// beyond the string; this is rarely a problem but it may generate protection +// violations, which could trigger fatal SIGABRT or SIGSEGV on Posix system +// - could be used instead of StrLen() when you are confident about your +// S input buffers, checking if cfSSE42 in CpuFeatures +function StrLenSSE42(S: pointer): PtrInt; +{$endif HASAESNI} + /// SSE 4.2 version of strspn(), to be used with PUTF8Char/PAnsiChar // - please note that this optimized version may read up to 15 bytes -// beyond the string, so should be avoided e.g. over memory mapped files +// beyond the string; this is rarely a problem but it may generate protection +// violations, which could trigger fatal SIGABRT or SIGSEGV on Posix system +// - could be used instead of strspn() when you are confident about your +// s/accept input buffers, checking if cfSSE42 in CpuFeatures function strspnsse42(s,accept: pointer): integer; /// SSE 4.2 version of strcspn(), to be used with PUTF8Char/PAnsiChar // - please note that this optimized version may read up to 15 bytes -// beyond the string, so should be avoided e.g. over memory mapped files +// beyond the string; this is rarely a problem but it may generate protection +// violations, which could trigger fatal SIGABRT or SIGSEGV on Posix system +// - could be used instead of strcspn() when you are confident about your +// s/reject input buffers, checking if cfSSE42 in CpuFeatures function strcspnsse42(s,reject: pointer): integer; -{$endif} - -/// fastest available version of strspn(), to be used with PUTF8Char/PAnsiChar -// - returns how many accept chars appear in the initial segment of s, e.g. -// ! strspn('abcdef','debca')=5 -// - will use SSE4.2 instructions on supported CPUs -// - please note that this function may read some bytes beyond the s string, so -// should be avoided e.g. over memory mapped files - use safe strspnpas instead -var strspn: function (s,accept: pointer): integer = strspnpas; - -/// fastest available version of strcspn(), to be used with PUTF8Char/PAnsiChar -// - returns how many reject chars do not appear in the initial segment of s, e.g. -// ! strcspn('1234,6789',',')=4 -// - will use SSE4.2 instructions on supported CPUs -// - please note that this function may read some bytes beyond the s string, so -// should be avoided e.g. over memory mapped files - use safe strcspnpas instead -var strcspn: function (s,reject: pointer): integer = strcspnpas; +{$endif CPUINTEL} +{$endif ABSOLUTEPASCAL} /// use our fast version of StrIComp(), to be used with PUTF8Char/PAnsiChar function StrIComp(Str1, Str2: pointer): PtrInt; @@ -2163,14 +2257,9 @@ function StrIComp(Str1, Str2: pointer): PtrInt; function StrLenPas(S: pointer): PtrInt; /// our fast version of StrLen(), to be used with PUTF8Char/PAnsiChar -// - this version will use fast SSE2/SSE4.2 instructions (if available), on both -// Win32 and Win64 platforms: please note that in this case, it may read up to -// 15 bytes before or beyond the string; this is rarely a problem but it can in -// principle generate a protection violation (e.g. when used over memory mapped files): -// you can use the slightly slower StrLenPas() function instead with such input; -// if you want to disable StrLenSSE42 for your whole project, add in the -// initialization section of one of your units: -// ! StrLen := @StrLenPas; +// - won't use SSE4.2 instructions on supported CPUs by default, which may read +// some bytes beyond the s string, so should be avoided e.g. over memory mapped +// files - call explicitely StrLenSSE42() if you are confident on your input var StrLen: function(S: pointer): PtrInt = StrLenPas; /// our fast version of FillChar() @@ -2304,11 +2393,11 @@ function GetQWord(P: PUTF8Char; var err: integer): QWord; // - set the err content to the index of any faulty character, 0 if conversion // was successful (same as the standard val function) function GetExtended(P: PUTF8Char; out err: integer): TSynExtended; overload; - {$ifdef FPC}inline;{$endif} // under Delphi XE4 64-bit compiler, it fails... /// get the extended floating point value stored in P^ // - this overloaded version returns 0 as a result if the content of P is invalid function GetExtended(P: PUTF8Char): TSynExtended; overload; + {$ifdef HASINLINE}inline;{$endif} /// get the WideChar stored in P^ (decode UTF-8 if necessary) // - any surrogate (UCS4>$ffff) will be returned as '?' @@ -2385,7 +2474,7 @@ function UrlDecode(U: PUTF8Char): RawUTF8; overload; // will return Next^='where=...' and V='*' // - if Upper is not found, Value is not modified, and result is FALSE // - if Upper is found, Value is modified with the supplied content, and result is TRUE -function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8; +function UrlDecodeValue(U: PUTF8Char; const Upper: RawUTF8; var Value: RawUTF8; Next: PPUTF8Char=nil): boolean; /// decode a specified parameter compatible with URI encoding into its original @@ -2394,7 +2483,7 @@ function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8; // will return Next^='where=...' and O=20 // - if Upper is not found, Value is not modified, and result is FALSE // - if Upper is found, Value is modified with the supplied content, and result is TRUE -function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar;var Value: integer; +function UrlDecodeInteger(U: PUTF8Char; const Upper: RawUTF8; var Value: integer; Next: PPUTF8Char=nil): boolean; /// decode a specified parameter compatible with URI encoding into its original @@ -2403,7 +2492,7 @@ function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar;var Value: integer; // will return Next^='where=...' and O=20 // - if Upper is not found, Value is not modified, and result is FALSE // - if Upper is found, Value is modified with the supplied content, and result is TRUE -function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal; +function UrlDecodeCardinal(U: PUTF8Char; const Upper: RawUTF8; var Value: Cardinal; Next: PPUTF8Char=nil): boolean; /// decode a specified parameter compatible with URI encoding into its original @@ -2412,7 +2501,7 @@ function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal; // will return Next^='where=...' and O=20 // - if Upper is not found, Value is not modified, and result is FALSE // - if Upper is found, Value is modified with the supplied content, and result is TRUE -function UrlDecodeInt64(U: PUTF8Char; Upper: PAnsiChar;var Value: Int64; +function UrlDecodeInt64(U: PUTF8Char; const Upper: RawUTF8; var Value: Int64; Next: PPUTF8Char=nil): boolean; /// decode a specified parameter compatible with URI encoding into its original @@ -2421,7 +2510,7 @@ function UrlDecodeInt64(U: PUTF8Char; Upper: PAnsiChar;var Value: Int64; // will return Next^='where=...' and P=20.45 // - if Upper is not found, Value is not modified, and result is FALSE // - if Upper is found, Value is modified with the supplied content, and result is TRUE -function UrlDecodeExtended(U: PUTF8Char; Upper: PAnsiChar; var Value: TSynExtended; +function UrlDecodeExtended(U: PUTF8Char; const Upper: RawUTF8; var Value: TSynExtended; Next: PPUTF8Char=nil): boolean; /// decode a specified parameter compatible with URI encoding into its original @@ -2430,7 +2519,7 @@ function UrlDecodeExtended(U: PUTF8Char; Upper: PAnsiChar; var Value: TSynExtend // will return Next^='where=...' and P=20.45 // - if Upper is not found, Value is not modified, and result is FALSE // - if Upper is found, Value is modified with the supplied content, and result is TRUE -function UrlDecodeDouble(U: PUTF8Char; Upper: PAnsiChar; var Value: double; +function UrlDecodeDouble(U: PUTF8Char; const Upper: RawUTF8; var Value: double; Next: PPUTF8Char=nil): boolean; /// returns TRUE if all supplied parameters do exist in the URI encoded text @@ -2506,30 +2595,30 @@ function JsonPropNameValid(P: PUTF8Char): boolean; // http://www.ietf.org/rfc/rfc4627.txt function NeedsJsonEscape(const Text: RawUTF8): boolean; -/// case unsensitive test of P1 and P2 content +/// case insensitive comparison of ASCII identifiers // - use it with property names values (i.e. only including A..Z,0..9,_ chars) function IdemPropName(const P1,P2: shortstring): boolean; overload; {$ifdef HASINLINE}inline;{$endif} -/// case unsensitive test of P1 and P2 content +/// case insensitive comparison of ASCII identifiers // - use it with property names values (i.e. only including A..Z,0..9,_ chars) // - this version expects P2 to be a PAnsiChar with a specified length function IdemPropName(const P1: shortstring; P2: PUTF8Char; P2Len: PtrInt): boolean; overload; {$ifdef HASINLINE}inline;{$endif} -/// case unsensitive test of P1 and P2 content +/// case insensitive comparison of ASCII identifiers // - use it with property names values (i.e. only including A..Z,0..9,_ chars) // - this version expects P1 and P2 to be a PAnsiChar with specified lengths function IdemPropName(P1,P2: PUTF8Char; P1Len,P2Len: PtrInt): boolean; overload; {$ifdef HASINLINE}inline;{$endif} -/// case unsensitive test of P1 and P2 content +/// case insensitive comparison of ASCII identifiers // - use it with property names values (i.e. only including A..Z,0..9,_ chars) // - this version expects P2 to be a PAnsiChar with specified length function IdemPropNameU(const P1: RawUTF8; P2: PUTF8Char; P2Len: PtrInt): boolean; overload; {$ifdef HASINLINE}inline;{$endif} -/// case unsensitive test of P1 and P2 content of same length +/// case insensitive comparison of ASCII identifiers of same length // - use it with property names values (i.e. only including A..Z,0..9,_ chars) // - this version expects P1 and P2 to be a PAnsiChar with an already checked // identical length, so may be used for a faster process, e.g. in a loop @@ -2539,7 +2628,7 @@ function IdemPropNameU(const P1: RawUTF8; P2: PUTF8Char; P2Len: PtrInt): boolean function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: PtrInt): boolean; {$ifndef ANDROID}{$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}{$endif} -/// case unsensitive test of P1 and P2 content +/// case insensitive comparison of ASCII identifiers // - use it with property names values (i.e. only including A..Z,0..9,_ chars) function IdemPropNameU(const P1,P2: RawUTF8): boolean; overload; {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif} @@ -2654,11 +2743,12 @@ function UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PAnsiChar; overlo /// copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion // - used internally for short keys match or case-insensitive hash -// - will use SSE4.2 instructions on supported CPUs - and potentiall read up -// to 15 bytes beyond the string: use UpperCopy255BufPas() for a safer memory read // - returns final dest pointer // - will copy up to 255 AnsiChar (expect the dest buffer to be defined e.g. as // array[byte] of AnsiChar on the caller stack) +// - won't use SSE4.2 instructions on supported CPUs by default, which may read +// some bytes beyond the s string, so should be avoided e.g. over memory mapped +// files - call explicitely UpperCopy255BufSSE42() if you are confident on your input var UpperCopy255Buf: function(dest: PAnsiChar; source: PUTF8Char; sourceLen: PtrInt): PAnsiChar; /// copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion @@ -2672,19 +2762,16 @@ function UpperCopy255BufPas(dest: PAnsiChar; source: PUTF8Char; sourceLen: PtrIn {$ifndef PUREPASCAL} {$ifndef DELPHI5OROLDER} - -/// copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion -// - used internally for short keys match or case-insensitive hash -// - this version will use SSE4.2 instructions on supported CPUs - and potentiall -// read up to 15 bytes beyond the string -// - you should not have to call this function, but rely on UpperCopy255Buf() -// - returns final dest pointer -// - will copy up to 255 AnsiChar (expect the dest buffer to be defined e.g. as -// array[byte] of AnsiChar on the caller stack) +/// SSE 4.2 version of UpperCopy255Buf() +// - copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion +// - please note that this optimized version may read up to 15 bytes +// beyond the string; this is rarely a problem but it may generate protection +// violations, which could trigger fatal SIGABRT or SIGSEGV on Posix system +// - could be used instead of UpperCopy255Buf() when you are confident about your +// dest/source input buffers, checking if cfSSE42 in CpuFeatures function UpperCopy255BufSSE42(dest: PAnsiChar; source: PUTF8Char; sourceLen: PtrInt): PAnsiChar; - -{$endif} -{$endif} +{$endif DELPHI5OROLDER} +{$endif PUREPASCAL} /// copy source into dest^ with WinAnsi 8 bits upper case conversion // - used internally for short keys match or case-insensitive hash @@ -2785,39 +2872,47 @@ function ConvertCaseUTF8(P: PUTF8Char; const Table: TNormTableByte): PtrInt; {$endif USENORMTOUPPER} +/// check if the supplied text has some case-insentitive 'a'..'z','A'..'Z' chars +// - will therefore be correct with true UTF-8 content, but only for 7 bit +function IsCaseSensitive(const S: RawUTF8): boolean; overload; + +/// check if the supplied text has some case-insentitive 'a'..'z','A'..'Z' chars +// - will therefore be correct with true UTF-8 content, but only for 7 bit +function IsCaseSensitive(P: PUTF8Char; PLen: integer): boolean; overload; + /// fast conversion of the supplied text into uppercase // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and -// will therefore by correct with true UTF-8 content, but only for 7 bit +// will therefore be correct with true UTF-8 content, but only for 7 bit function UpperCase(const S: RawUTF8): RawUTF8; /// fast conversion of the supplied text into uppercase // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and -// will therefore by correct with true UTF-8 content, but only for 7 bit +// will therefore be correct with true UTF-8 content, but only for 7 bit procedure UpperCaseCopy(Text: PUTF8Char; Len: integer; var result: RawUTF8); overload; /// fast conversion of the supplied text into uppercase // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and -// will therefore by correct with true UTF-8 content, but only for 7 bit +// will therefore be correct with true UTF-8 content, but only for 7 bit procedure UpperCaseCopy(const Source: RawUTF8; var Dest: RawUTF8); overload; /// fast in-place conversion of the supplied variable text into uppercase // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and -// will therefore by correct with true UTF-8 content, but only for 7 bit +// will therefore be correct with true UTF-8 content, but only for 7 bit procedure UpperCaseSelf(var S: RawUTF8); /// fast conversion of the supplied text into lowercase // - this will only convert 'A'..'Z' into 'a'..'z' (no NormToLower use), and -// will therefore by correct with true UTF-8 content +// will therefore be correct with true UTF-8 content function LowerCase(const S: RawUTF8): RawUTF8; /// fast conversion of the supplied text into lowercase // - this will only convert 'A'..'Z' into 'a'..'z' (no NormToLower use), and -// will therefore by correct with true UTF-8 content +// will therefore be correct with true UTF-8 content procedure LowerCaseCopy(Text: PUTF8Char; Len: integer; var result: RawUTF8); /// fast in-place conversion of the supplied variable text into lowercase // - this will only convert 'A'..'Z' into 'a'..'z' (no NormToLower use), and -// will therefore by correct with true UTF-8 content, but only for 7 bit +// will therefore be correct with true UTF-8 content, but only for 7 bit procedure LowerCaseSelf(var S: RawUTF8); /// accurate conversion of the supplied UTF-8 content into the corresponding @@ -2871,10 +2966,8 @@ function FindIniEntryW(const Content: string; const Section, Name: RawUTF8): str {$ifdef PUREPASCAL} {$ifdef HASINLINE} -function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt=1): PtrInt; inline; -{$ifndef FPC} function PosExPas(pSub, p: PUTF8Char; Offset: PtrUInt): PtrInt; -{$endif} +function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt=1): PtrInt; inline; {$else} var PosEx: function(const SubStr, S: RawUTF8; Offset: PtrUInt=1): PtrInt; {$endif} @@ -2885,6 +2978,10 @@ function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt=1): integer; {$endif PUREPASCAL} +/// optimized version of PosEx() with search text as one AnsiChar +function PosExChar(Chr: AnsiChar; const Str: RawUTF8): PtrInt; + {$ifdef HASINLINE}inline;{$endif} + /// split a RawUTF8 string into two strings, according to SepStr separator // - if SepStr is not found, LeftStr=Str and RightStr='' // - if ToUpperCase is TRUE, then LeftStr and RightStr will be made uppercase @@ -2955,7 +3052,8 @@ procedure QuotedStr(Text: PUTF8Char; Quote: AnsiChar; var result: RawUTF8); over /// convert a buffered text content into a JSON string // - with proper escaping of the content, and surounding " characters -procedure QuotedStrJSON(const aText: RawUTF8; var result: RawUTF8); +procedure QuotedStrJSON(const aText: RawUTF8; var result: RawUTF8; + const aPrefix: RawUTF8=''; const aSuffix: RawUTF8=''); /// unquote a SQL-compatible string // - the first character in P^ must be either ', either " then double quotes @@ -3031,8 +3129,16 @@ function IdemPCharAndGetNextItem(var source: PUTF8Char; const searchUp: RawUTF8; function GotoNextLine(source: PUTF8Char): PUTF8Char; {$ifdef HASINLINE}inline;{$endif} +/// compute the line length from a size-delimited source array of chars +// - will use fast assembly on x86-64 CPU, and expects TextEnd to be not nil +// - is likely to read some bytes after the TextEnd buffer, so GetLineSize() +// may be preferred, e.g. on memory mapped files +function BufferLineLength(Text, TextEnd: PUTF8Char): PtrInt; + {$ifndef CPUX64}{$ifdef FPC}inline;{$endif}{$endif} + /// compute the line length from source array of chars -// - end counting at either #0, #13 or #10 +// - if PEnd = nil, end counting at either #0, #13 or #10 +// - otherwise, end counting at either #13 or #10 function GetLineSize(P,PEnd: PUTF8Char): PtrUInt; /// returns true if the line length from source array of chars is not less than @@ -3048,6 +3154,10 @@ function GetNextItem(var P: PUTF8Char; Sep: AnsiChar= ','): RawUTF8; overload; // - P=nil after call when end of text is reached procedure GetNextItem(var P: PUTF8Char; Sep: AnsiChar; var result: RawUTF8); overload; +/// return next CSV string (unquoted if needed) from P +// - P=nil after call when end of text is reached +procedure GetNextItem(var P: PUTF8Char; Sep, Quote: AnsiChar; var result: RawUTF8); overload; + /// return trimmed next CSV string from P // - P=nil after call when end of text is reached procedure GetNextItemTrimed(var P: PUTF8Char; Sep: AnsiChar; var result: RawUTF8); @@ -3111,19 +3221,27 @@ function GetNextTChar64(var P: PUTF8Char; Sep: AnsiChar; out Buf: TChar64): PtrI /// return next CSV string as unsigned integer from P, 0 if no more // - if Sep is #0, it won't be searched for -function GetNextItemCardinal(var P: PUTF8Char; Sep: AnsiChar= ','): PtrUInt; +function GetNextItemCardinal(var P: PUTF8Char; Sep: AnsiChar=','): PtrUInt; /// return next CSV string as signed integer from P, 0 if no more // - if Sep is #0, it won't be searched for -function GetNextItemInteger(var P: PUTF8Char; Sep: AnsiChar= ','): PtrInt; +function GetNextItemInteger(var P: PUTF8Char; Sep: AnsiChar=','): PtrInt; + +/// return next CSV string as 64-bit signed integer from P, 0 if no more +// - if Sep is #0, it won't be searched for +function GetNextItemInt64(var P: PUTF8Char; Sep: AnsiChar=','): Int64; -/// return next CSV string as 64 bit signed integer from P, 0 if no more +/// return next CSV string as 64-bit unsigned integer from P, 0 if no more // - if Sep is #0, it won't be searched for -function GetNextItemInt64(var P: PUTF8Char; Sep: AnsiChar= ','): Int64; +function GetNextItemQWord(var P: PUTF8Char; Sep: AnsiChar=','): QWord; -/// return next CSV string as 64 bit unsigned integer from P, 0 if no more +/// return next CSV hexadecimal string as 64-bit unsigned integer from P +// - returns 0 if no valid hexadecimal text is available in P // - if Sep is #0, it won't be searched for -function GetNextItemQWord(var P: PUTF8Char; Sep: AnsiChar= ','): QWord; +// - will first fill the 64-bit value with 0, then decode each two hexadecimal +// characters available in P +// - could be used to decode TTextWriter.AddBinToHexDisplayMinChars() output +function GetNextItemHexa(var P: PUTF8Char; Sep: AnsiChar=','): QWord; /// return next CSV string as unsigned integer from P, 0 if no more // - P^ will point to the first non digit character (the item separator, e.g. @@ -3132,28 +3250,31 @@ function GetNextItemCardinalStrict(var P: PUTF8Char): PtrUInt; /// return next CSV string as unsigned integer from P, 0 if no more // - this version expects P^ to point to an Unicode char array -function GetNextItemCardinalW(var P: PWideChar; Sep: WideChar= ','): PtrUInt; +function GetNextItemCardinalW(var P: PWideChar; Sep: WideChar=','): PtrUInt; /// return next CSV string as double from P, 0.0 if no more // - if Sep is #0, will return all characters until next whitespace char -function GetNextItemDouble(var P: PUTF8Char; Sep: AnsiChar= ','): double; +function GetNextItemDouble(var P: PUTF8Char; Sep: AnsiChar=','): double; /// return next CSV string as currency from P, 0.0 if no more // - if Sep is #0, will return all characters until next whitespace char -function GetNextItemCurrency(var P: PUTF8Char; Sep: AnsiChar= ','): currency; overload; +function GetNextItemCurrency(var P: PUTF8Char; Sep: AnsiChar=','): currency; overload; {$ifdef HASINLINE}inline;{$endif} /// return next CSV string as currency from P, 0.0 if no more // - if Sep is #0, will return all characters until next whitespace char -procedure GetNextItemCurrency(var P: PUTF8Char; out result: currency; Sep: AnsiChar= ','); overload; +procedure GetNextItemCurrency(var P: PUTF8Char; out result: currency; Sep: AnsiChar=','); overload; /// return n-th indexed CSV string in P, starting at Index=0 for first one -function GetCSVItem(P: PUTF8Char; Index: PtrUInt; Sep: AnsiChar = ','): RawUTF8; +function GetCSVItem(P: PUTF8Char; Index: PtrUInt; Sep: AnsiChar=','): RawUTF8; overload; + +/// return n-th indexed CSV string (unquoted if needed) in P, starting at Index=0 for first one +function GetUnQuoteCSVItem(P: PUTF8Char; Index: PtrUInt; Sep: AnsiChar=','; Quote: AnsiChar=''''): RawUTF8; overload; /// return n-th indexed CSV string in P, starting at Index=0 for first one // - this function return the generic string type of the compiler, and // therefore can be used with ready to be displayed text (i.e. the VCL) -function GetCSVItemString(P: PChar; Index: PtrUInt; Sep: Char = ','): string; +function GetCSVItemString(P: PChar; Index: PtrUInt; Sep: Char=','): string; /// return last CSV string in the supplied UTF-8 content function GetLastCSVItem(const CSV: RawUTF8; Sep: AnsiChar=','): RawUTF8; @@ -3227,7 +3348,7 @@ procedure AddRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer; type /// simple stack-allocated type for handling a type names list - {$ifdef UNICODE}TPropNameList = record{$else}TPropNameList = object{$endif} + {$ifdef FPC_OR_UNICODE}TPropNameList = record{$else}TPropNameList = object{$endif} public Values: TRawUTF8DynArray; Count: Integer; @@ -3487,9 +3608,14 @@ function DirectoryDeleteOlderFiles(const Directory: TFileName; TimePeriod: TDate /// creates a directory if not already existing // - returns the full expanded directory name, including trailing backslash +// - returns '' on error, unless RaiseExceptionOnCreationFailure=true function EnsureDirectoryExists(const Directory: TFileName; RaiseExceptionOnCreationFailure: boolean=false): TFileName; +/// check if the directory is writable for the current user +// - try to write a small file with a random name +function IsDirectoryWritable(const Directory: TFileName): boolean; + /// compute an unique temporary file name // - following 'exename_01234567.tmp' pattern, in the system temporary folder function TemporaryFileName: TFileName; @@ -3497,7 +3623,7 @@ function TemporaryFileName: TFileName; type {$A-} /// file found result item, as returned by FindFiles() - {$ifdef UNICODE}TFindFiles = record{$else}TFindFiles = object{$endif} + {$ifdef FPC_OR_UNICODE}TFindFiles = record{$else}TFindFiles = object{$endif} public /// the matching file name, including its folder name Name: TFileName; @@ -3617,8 +3743,8 @@ function FindObjectEntryWithoutExt(const Content, Name: RawUTF8): RawUTF8; // in a huge text buffer // - this version also handles french and spanish pronunciations on request, // which differs from default Soundex, i.e. English - {$ifdef UNICODE}TSynSoundEx = record{$else}TSynSoundEx = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TSynSoundEx = record private + {$else}TSynSoundEx = object protected{$endif} Search, FirstChar: cardinal; fValues: PSoundExValues; public @@ -3697,8 +3823,13 @@ function TrimLeftLowerCaseShort(V: PShortString): RawUTF8; /// trim first lowercase chars ('otDone' will return 'Done' e.g.) // - return a shortstring: enumeration names are pure 7bit ANSI with Delphi 7 // to 2007, and UTF-8 encoded with Delphi 2009+ -function TrimLeftLowerCaseToShort(V: PShortString): ShortString; +function TrimLeftLowerCaseToShort(V: PShortString): ShortString; overload; + {$ifdef HASINLINE}inline;{$endif} +/// trim first lowercase chars ('otDone' will return 'Done' e.g.) +// - return a shortstring: enumeration names are pure 7bit ANSI with Delphi 7 +// to 2007, and UTF-8 encoded with Delphi 2009+ +procedure TrimLeftLowerCaseToShort(V: PShortString; out result: ShortString); overload; /// convert a CamelCase string into a space separated one // - 'OnLine' will return 'On line' e.g., and 'OnMyLINE' will return 'On my LINE' @@ -3729,13 +3860,13 @@ function UnCamelCase(D, P: PUTF8Char): integer; overload; /// convert a string into an human-friendly CamelCase identifier // - replacing spaces or punctuations by an uppercase character -// - it is not the reverse function to UnCamelCase() +// - as such, it is not the reverse function to UnCamelCase() procedure CamelCase(P: PAnsiChar; len: integer; var s: RawUTF8; const isWord: TSynByteSet=[ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z')]); overload; /// convert a string into an human-friendly CamelCase identifier // - replacing spaces or punctuations by an uppercase character -// - it is not the reverse function to UnCamelCase() +// - as such, it is not the reverse function to UnCamelCase() procedure CamelCase(const text: RawUTF8; var s: RawUTF8; const isWord: TSynByteSet=[ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z')]); overload; {$ifdef HASINLINE}inline;{$endif} @@ -3997,19 +4128,19 @@ function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardin // - return -1 if Value was not found function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): PtrInt; -/// fast search of an integer position in a 64 bit integer array +/// fast search of an integer position in a 64-bit integer array // - Count is the number of Int64 entries in P^ // - returns P where P^=Value // - returns nil if Value was not found function Int64Scan(P: PInt64Array; Count: PtrInt; const Value: Int64): PInt64; -/// fast search of an integer position in a signed 64 bit integer array +/// fast search of an integer position in a signed 64-bit integer array // - Count is the number of Int64 entries in P^ // - returns index of P^[index]=Value // - returns -1 if Value was not found function Int64ScanIndex(P: PInt64Array; Count: PtrInt; const Value: Int64): PtrInt; -/// fast search of an integer position in an unsigned 64 bit integer array +/// fast search of an integer position in an unsigned 64-bit integer array // - Count is the number of QWord entries in P^ // - returns index of P^[index]=Value // - returns -1 if Value was not found @@ -4021,7 +4152,7 @@ function QWordScanIndex(P: PQWordArray; Count: PtrInt; const Value: QWord): PtrI // - returns false if Value was not found function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean; -/// fast search of an integer value in a 64 bit integer array +/// fast search of an integer value in a 64-bit integer array // - returns true if P^=Value within Count entries // - returns false if Value was not found function Int64ScanExists(P: PInt64Array; Count: PtrInt; const Value: Int64): boolean; @@ -4061,15 +4192,15 @@ procedure QuickSortInteger(var ID: TIntegerDynArray); overload; /// sort a 16 bit unsigned Integer array, low values first procedure QuickSortWord(ID: PWordArray; L, R: PtrInt); -/// sort a 64 bit signed Integer array, low values first +/// sort a 64-bit signed Integer array, low values first procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); overload; -/// sort a 64 bit unsigned Integer array, low values first +/// sort a 64-bit unsigned Integer array, low values first // - QWord comparison are implemented correctly under FPC or Delphi 2009+ - // older compilers will use fast and exact SortDynArrayQWord() procedure QuickSortQWord(ID: PQWordArray; L, R: PtrInt); overload; -/// sort a 64 bit Integer array, low values first +/// sort a 64-bit Integer array, low values first procedure QuickSortInt64(ID,CoValues: PInt64Array; L, R: PtrInt); overload; type @@ -4106,13 +4237,13 @@ function FastFindIntegerSorted(const Values: TIntegerDynArray; Value: integer): /// fast O(log(n)) binary search of a 16 bit unsigned integer value in a sorted array function FastFindWordSorted(P: PWordArray; R: PtrInt; Value: Word): PtrInt; -/// fast O(log(n)) binary search of a 64 bit signed integer value in a sorted array +/// fast O(log(n)) binary search of a 64-bit signed integer value in a sorted array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - return index of P^[result]=Value // - return -1 if Value was not found function FastFindInt64Sorted(P: PInt64Array; R: PtrInt; const Value: Int64): PtrInt; overload; -/// fast O(log(n)) binary search of a 64 bit unsigned integer value in a sorted array +/// fast O(log(n)) binary search of a 64-bit unsigned integer value in a sorted array // - R is the last index of available integer entries in P^ (i.e. Count-1) // - return index of P^[result]=Value // - return -1 if Value was not found @@ -4179,6 +4310,9 @@ procedure AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer); overload; {$ifdef HASINLINE}inline;{$endif} +/// add an integer array at the end of a dynamic array of integer +function AddInteger(var Values: TIntegerDynArray; const Another: TIntegerDynArray): PtrInt; overload; + /// add an integer value at the end of a dynamic array of integers // - this overloaded function will use a separate Count variable (faster), // and would allow to search for duplicates @@ -4189,18 +4323,21 @@ function AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer; NoDuplicates: boolean): boolean; overload; /// add a 16-bit integer value at the end of a dynamic array of integers -function AddWord(var Values: TWordDynArray; var ValuesCount: integer; Value: Word): integer; +function AddWord(var Values: TWordDynArray; var ValuesCount: integer; Value: Word): PtrInt; /// add a 64-bit integer value at the end of a dynamic array of integers -function AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64): integer; overload; +function AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} -/// add a 64-bit integer value at the end of a dynamic array of integers -function AddInt64(var Values: TInt64DynArray; Value: Int64): integer; overload; +/// add a 64-bit integer value at the end of a dynamic array +function AddInt64(var Values: TInt64DynArray; Value: Int64): PtrInt; overload; {$ifdef HASINLINE}inline;{$endif} +/// add a 64-bit integer array at the end of a dynamic array +function AddInt64(var Values: TInt64DynArray; const Another: TInt64DynArray): PtrInt; overload; + /// if not already existing, add a 64-bit integer value to a dynamic array -function AddInt64Once(var Values: TInt64DynArray; Value: Int64): integer; +function AddInt64Once(var Values: TInt64DynArray; Value: Int64): PtrInt; /// if not already existing, add a 64-bit integer value to a sorted dynamic array procedure AddInt64Sorted(var Values: TInt64DynArray; Value: Int64); @@ -4217,6 +4354,12 @@ procedure DeleteInteger(var Values: TIntegerDynArray; var ValuesCount: Integer; procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray; ExcludedSortSize: Integer=32); +/// ensure some 32-bit integer from Values[] will only contain Included[] +// - Included is declared as var, since it will be sorted in-place during process +// if it contains more than IncludedSortSize items (i.e. if the sort is worth it) +procedure IncludeInteger(var Values, Included: TIntegerDynArray; + IncludedSortSize: Integer=32); + /// sort and remove any 32-bit duplicated integer from Values[] procedure DeduplicateInteger(var Values: TIntegerDynArray); overload; @@ -4245,6 +4388,12 @@ procedure DeleteInt64(var Values: TInt64DynArray; var ValuesCount: Integer; Inde procedure ExcludeInt64(var Values, Excluded: TInt64DynArray; ExcludedSortSize: Integer=32); +/// ensure some 64-bit integer from Values[] will only contain Included[] +// - Included is declared as var, since it will be sorted in-place during process +// if it contains more than IncludedSortSize items (i.e. if the sort is worth it) +procedure IncludeInt64(var Values, Included: TInt64DynArray; + IncludedSortSize: Integer=32); + /// sort and remove any 64-bit duplicated integer from Values[] procedure DeduplicateInt64(var Values: TInt64DynArray); overload; @@ -4351,7 +4500,7 @@ function FromU64(const Values: array of QWord): TQWordDynArray; // - is defined either as an object either as a record, due to a bug // in Delphi 2009/2010 compiler (at least): this structure is not initialized // if defined as an object on the stack, but will be as a record :( - {$ifdef UNICODE}TSortedWordArray = record{$else}TSortedWordArray = object{$endif} + {$ifdef FPC_OR_UNICODE}TSortedWordArray = record{$else}TSortedWordArray = object{$endif} public Values: TWordDynArray; Count: integer; @@ -4537,6 +4686,20 @@ function FromVarBlob(Data: PByte): TValueResult; {$ifdef HASINLINE}inline;{$endi /// internal set to specify some standard Delphi arrays TDynArrayKinds = set of TDynArrayKind; + {$ifdef FPC} + /// map the Delphi/FPC dynamic array header (stored before each instance) + // - define globally for proper inlining with FPC + TDynArrayRec = packed record + /// dynamic array reference count (basic garbage memory mechanism) + refCnt: PtrInt; + high: tdynarrayindex; + function GetLength: sizeint; inline; + procedure SetLength(len: sizeint); inline; + property length: sizeint read GetLength write SetLength; + end; + PDynArrayRec = ^TDynArrayRec; + {$endif FPC} + function ToText(k: TDynArrayKind): PShortString; overload; const @@ -4580,13 +4743,8 @@ function ToText(k: TDynArrayKind): PShortString; overload; // - is defined either as an object either as a record, due to a bug // in Delphi 2009/2010 compiler (at least): this structure is not initialized // if defined as an object on the stack, but will be as a record :( - {$ifdef UNDIRECTDYNARRAY} - TDynArray = record - private - {$else} - TDynArray = object - protected - {$endif} + {$ifdef UNDIRECTDYNARRAY}TDynArray = record private + {$else}TDynArray = object protected{$endif} fValue: PPointer; fTypeInfo: pointer; fElemType: pointer; @@ -4812,12 +4970,18 @@ TDynArray = record /// sort the dynamic array elements, using the Compare property function // - it will change the dynamic array content, and exchange all elements // in order to be sorted in increasing order according to Compare function - procedure Sort(aCompare: TDynArraySortCompare=nil); + procedure Sort(aCompare: TDynArraySortCompare=nil); overload; /// sort some dynamic array elements, using the Compare property function // - this method allows to sort only some part of the items // - it will change the dynamic array content, and exchange all elements // in order to be sorted in increasing order according to Compare function procedure SortRange(aStart, aStop: integer; aCompare: TDynArraySortCompare=nil); + /// sort the dynamic array elements, using a Compare method (not function) + // - it will change the dynamic array content, and exchange all elements + // in order to be sorted in increasing order according to Compare function, + // unless aReverse is true + // - it won't mark the array as Sorted, since the comparer is local + procedure Sort(const aCompare: TEventDynArraySortCompare; aReverse: boolean=false); overload; /// search the elements range which match a given value in a sorted dynamic array // - this method will use the Compare property function for the search // - returns TRUE and the matching indexes, or FALSE if none found @@ -5003,17 +5167,19 @@ TDynArray = record /// set all content of one dynamic array to the current array // - both must be of the same exact type // - T*ObjArray will be reallocated and copied by content (using a temporary - // JSON serialization), unless ObjArrayByRef is true + // JSON serialization), unless ObjArrayByRef is true and pointers are copied procedure Copy(const Source: TDynArray; ObjArrayByRef: boolean=false); /// set all content of one dynamic array to the current array // - both must be of the same exact type // - T*ObjArray will be reallocated and copied by content (using a temporary - // JSON serialization), unless ObjArrayByRef is true + // JSON serialization), unless ObjArrayByRef is true and pointers are copied procedure CopyFrom(const Source; MaxElem: integer; ObjArrayByRef: boolean=false); /// set all content of the current dynamic array to another array variable // - both must be of the same exact type + // - resulting length(Dest) will match the exact items count, even if an + // external Count integer variable is used by this instance // - T*ObjArray will be reallocated and copied by content (using a temporary - // JSON serialization), unless ObjArrayByRef is true + // JSON serialization), unless ObjArrayByRef is true and pointers are copied procedure CopyTo(out Dest; ObjArrayByRef: boolean=false); {$endif DELPHI5OROLDER} /// returns a pointer to an element of the array @@ -5027,7 +5193,7 @@ TDynArray = record // - do nothing if index is out of range procedure ElemCopyAt(index: PtrInt; var Dest); {$ifdef FPC}inline;{$endif} /// will move one element content from its index into another variable - // - will erase the internal item ater copy + // - will erase the internal item after copy // - do nothing if index is out of range procedure ElemMoveTo(index: PtrInt; var Dest); /// will copy one variable content into an indexed element @@ -5126,8 +5292,8 @@ TDynArray = record /// allows to iterate over a TDynArray.SaveTo binary buffer // - may be used as alternative to TDynArray.LoadFrom, if you don't want // to allocate all items at once, but retrieve items one by one - {$ifdef UNICODE}TDynArrayLoadFrom = record{$else}TDynArrayLoadFrom = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TDynArrayLoadFrom = record private + {$else}TDynArrayLoadFrom = object protected{$endif} DynArray: TDynArray; // used to access RTTI Hash: PCardinalArray; public @@ -5212,6 +5378,8 @@ TDynArrayHashed = record function KnownType: TDynArrayKind; inline; procedure Clear; inline; procedure ElemCopy(const A; var B); inline; + function ElemPtr(index: PtrInt): pointer; inline; + procedure ElemCopyAt(index: PtrInt; var Dest); inline; // warning: you shall call ReHash() after manual Add/Delete function Add(const Elem): integer; inline; procedure Delete(aIndex: PtrInt); inline; @@ -5609,13 +5777,13 @@ TSynPersistent = class(TObject) // for this class hierarchy: as a result, you would NOT be able to // implement an interface with a TSynPersistent descendent (but you should // not need to, but inherit from TInterfacedObject) - // - warning: under FPC, it won't initialize management operators + // - warning: under FPC, it won't initialize fields management operators class function NewInstance: TObject; override; {$ifndef FPC_OR_PUREPASCAL} /// optimized x86 asm finalization code // - warning: this version won't release either any allocated TMonitor // (as available since Delphi 2009) - do not use TMonitor with - // TSynPersistent, but rather the faster TSynPersistentLocked class + // TSynPersistent, but rather the faster TSynPersistentLock class procedure FreeInstance; override; {$endif} end; @@ -5630,9 +5798,12 @@ TSynPersistent = class(TObject) // @http://www.delphitools.info/2011/11/30/fixing-tcriticalsection // - internal padding is used to safely store up to 7 values protected // from concurrent access with a mutex - {$ifdef UNICODE}TSynLocker = record{$else}TSynLocker = object{$endif} - private + // - for object-level locking, see TSynPersistentLock which owns one such + // instance, or call low-level NewSynLocker function then DoneAndFreemem + {$ifdef FPC_OR_UNICODE}TSynLocker = record private + {$else}TSynLocker = object protected{$endif} fSection: TRTLCriticalSection; + fSectionPadding: PtrInt; // paranoid to avoid FUTEX_WAKE_PRIVATE=EAGAIN fLocked, fInitialized: boolean; {$ifndef NOVARIANTS} function GetVariant(Index: integer): Variant; @@ -5672,6 +5843,9 @@ {$ifdef UNICODE}TSynLocker = record{$else}TSynLocker = object{$endif} // the TSynLocker instance), otherwise you may encounter unexpected // behavior, like access violations or memory leaks procedure Done; + /// finalize the mutex, and call FreeMem() on the pointer of this instance + // - should have been initiazed with a NewSynLocker call + procedure DoneAndFreeMem; /// lock the instance for exclusive access // - use as such to avoid race condition (from a Safe: TSynLocker property): // ! Safe.Lock; @@ -5794,23 +5968,26 @@ {$ifdef UNICODE}TSynLocker = record{$else}TSynLocker = object{$endif} /// adding locking methods to a TSynPersistent with virtual constructor // - you may use this class instead of the RTL TCriticalSection, since it // would use a TSynLocker which does not suffer from CPU cache line conflit - TSynPersistentLocked = class(TSynPersistent) + TSynPersistentLock = class(TSynPersistent) protected - fSafe: TSynLocker; + fSafe: PSynLocker; // TSynLocker would increase inherited fields offset public - /// initialize the object instance, and its associated lock + /// initialize the instance, and its associated lock constructor Create; override; - /// release the instance (including the locking resource) + /// finalize the instance, and its associated lock destructor Destroy; override; - /// access to the locking methods of this instance - // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block - property Safe: TSynLocker read fSafe; + /// access to the associated instance critical section + // - call Safe.Lock/UnLock to protect multi-thread access on this storage + property Safe: PSynLocker read fSafe; end; + /// used for backward compatibility only with existing code + TSynPersistentLocked = class(TSynPersistentLock); + /// adding locking methods to a TInterfacedObject with virtual constructor TInterfacedObjectLocked = class(TInterfacedObjectWithCustomCreate) protected - fSafe: TSynLocker; + fSafe: PSynLocker; // TSynLocker would increase inherited fields offset public /// initialize the object instance, and its associated lock constructor Create; override; @@ -5818,7 +5995,7 @@ TInterfacedObjectLocked = class(TInterfacedObjectWithCustomCreate) destructor Destroy; override; /// access to the locking methods of this instance // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block - property Safe: TSynLocker read fSafe; + property Safe: PSynLocker read fSafe; end; /// used to determine the exact class type of a TInterfacedObjectWithCustomCreate @@ -5834,73 +6011,8 @@ TPersistentWithCustomCreateClass = class of TPersistentWithCustomCreate; TSynPersistentClass = class of TSynPersistent; - /// internal item definition, used by TPendingTaskList storage - TPendingTaskListItem = packed record - /// the task should be executed when TPendingTaskList.GetTimestamp reaches - // this value - Timestamp: Int64; - /// the associated task, stored by representation as raw binary - Task: RawByteString; - end; - /// internal list definition, used by TPendingTaskList storage - TPendingTaskListItemDynArray = array of TPendingTaskListItem; - - /// handle a list of tasks, stored as RawByteString, with a time stamp - // - internal time stamps would be GetTickCount64 by default, so have a - // resolution of about 16 ms under Windows - // - you can add tasks to the internal list, to be executed after a given - // delay, using a post/peek like algorithm - // - execution delays are not expected to be accurate, but are best guess, - // according to NextTask call - // - this implementation is thread-safe, thanks to the Safe internal locker - TPendingTaskList = class(TSynPersistent) - protected - fCount: Integer; - fTask: TPendingTaskListItemDynArray; - fTasks: TDynArray; - fSafe: TSynLocker; - function GetCount: integer; - function GetTimestamp: Int64; virtual; - public - /// initialize the list memory and resources - constructor Create; override; - /// finaalize the list memory and resources - destructor Destroy; override; - /// append a task, specifying a delay in milliseconds from current time - procedure AddTask(aMilliSecondsDelayFromNow: integer; const aTask: RawByteString); virtual; - /// append several tasks, specifying a delay in milliseconds between tasks - // - first supplied delay would be computed from the current time, then - // it would specify how much time to wait between the next supplied task - procedure AddTasks(const aMilliSecondsDelays: array of integer; - const aTasks: array of RawByteString); - /// retrieve the next pending task - // - returns '' if there is no scheduled task available at the current time - // - returns the next stack as defined corresponding to its specified delay - function NextPendingTask: RawByteString; virtual; - /// flush all pending tasks - procedure Clear; virtual; - /// access to the locking methods of this instance - // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block - property Safe: TSynlocker read fSafe; - /// access to the internal TPendingTaskListItem.Timestamp stored value - // - corresponding to the current time - // - default implementation is to return GetTickCount64, with a 16 ms - // typical resolution under Windows - property Timestamp: Int64 read GetTimestamp; - /// how many pending tasks are currently defined - property Count: integer read GetCount; - /// direct low-level access to the internal task list - // - warning: this dynamic array length is the list capacity: use Count - // property to retrieve the exact number of stored items - // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block for - // thread-safe access to this array - // - items are stored in increasing Timestamp, i.e. the first item is - // the next one which would be returned by the NextPendingTask method - property Task: TPendingTaskListItemDynArray read fTask; - end; - /// used to store one list of hashed RawUTF8 in TRawUTF8Interning pool - {$ifdef UNICODE}TRawUTF8InterningSlot = record{$else}TRawUTF8InterningSlot = object{$endif} + {$ifdef FPC_OR_UNICODE}TRawUTF8InterningSlot = record{$else}TRawUTF8InterningSlot = object{$endif} public /// actual RawUTF8 storage Value: TRawUTF8DynArray; @@ -5908,7 +6020,7 @@ {$ifdef UNICODE}TRawUTF8InterningSlot = record{$else}TRawUTF8InterningSlot = o Values: TDynArrayHashed; /// associated mutex for thread-safe process Safe: TSynLocker; - /// initialize the RawUTF8 slot + /// initialize the RawUTF8 slot (and its Safe mutex) procedure Init; /// finalize the RawUTF8 slot procedure Done; @@ -6027,8 +6139,8 @@ TSynNameValueItem = record // - is defined either as an object either as a record, due to a bug // in Delphi 2009/2010 compiler (at least): this structure is not initialized // if defined as an object on the stack, but will be as a record :( - {$ifdef UNICODE}TSynNameValue = record{$else}TSynNameValue = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TSynNameValue = record private + {$else}TSynNameValue = object protected{$endif} fDynArray: TDynArrayHashed; fOnAdd: TSynNameValueNotify; function GetBlobData: RawByteString; @@ -6152,6 +6264,11 @@ {$ifdef UNICODE}TSynNameValue = record{$else}TSynNameValue = object{$endif} /// a reference pointer to a Name/Value RawUTF8 pairs storage PSynNameValue = ^TSynNameValue; +/// allocate and initialize a TSynLocker instance +// - caller should call result^.DoneAndFreemem when not used any more +function NewSynLocker: PSynLocker; + {$ifdef HASINLINE}inline;{$endif} + /// wrapper to add an item to a array of pointer dynamic array storage function PtrArrayAdd(var aPtrArray; aItem: pointer): integer; {$ifdef HASINLINE}inline;{$endif} @@ -6183,21 +6300,21 @@ function PtrArrayFind(var aPtrArray; aItem: pointer): integer; // ! ObjArrayClear(arr); // release all items // ! end; // - return the index of the item in the dynamic array -function ObjArrayAdd(var aObjArray; aItem: TObject): integer; +function ObjArrayAdd(var aObjArray; aItem: TObject): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// wrapper to add items to a T*ObjArray dynamic array storage // - aSourceObjArray[] items will be owned by aDestObjArray[], therefore // aSourceObjArray is set to nil // - return the new number of the items in aDestObjArray -function ObjArrayAppend(var aDestObjArray, aSourceObjArray): integer; +function ObjArrayAppend(var aDestObjArray, aSourceObjArray): PtrInt; /// wrapper to add an item to a T*ObjArray dynamic array storage // - this overloaded function will use a separated variable to store the items // count, so will be slightly faster: but you should call SetLength() when done, // to have an array as expected by TJSONSerializer.RegisterObjArrayForJSON() // - return the index of the item in the dynamic array -function ObjArrayAddCount(var aObjArray; aItem: TObject; var aObjArrayCount: integer): integer; +function ObjArrayAddCount(var aObjArray; aItem: TObject; var aObjArrayCount: integer): PtrInt; /// wrapper to add once an item to a T*ObjArray dynamic array storage // - as expected by TJSONSerializer.RegisterObjArrayForJSON() @@ -6216,7 +6333,7 @@ procedure ObjArraySetLength(var aObjArray; aLength: integer); // - as expected by TJSONSerializer.RegisterObjArrayForJSON() // - search is performed by address/reference, not by content // - returns -1 if the item is not found in the dynamic array -function ObjArrayFind(const aObjArray; aItem: TObject): integer; +function ObjArrayFind(const aObjArray; aItem: TObject): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// wrapper to count all not nil items in a T*ObjArray dynamic array storage @@ -6226,14 +6343,14 @@ function ObjArrayCount(const aObjArray): integer; /// wrapper to delete an item in a T*ObjArray dynamic array storage // - as expected by TJSONSerializer.RegisterObjArrayForJSON() // - do nothing if the index is out of range in the dynamic array -procedure ObjArrayDelete(var aObjArray; aItemIndex: integer; +procedure ObjArrayDelete(var aObjArray; aItemIndex: PtrInt; aContinueOnException: boolean=false); overload; /// wrapper to delete an item in a T*ObjArray dynamic array storage // - as expected by TJSONSerializer.RegisterObjArrayForJSON() // - search is performed by address/reference, not by content // - do nothing if the item is not found in the dynamic array -function ObjArrayDelete(var aObjArray; aItem: TObject): integer; overload; +function ObjArrayDelete(var aObjArray; aItem: TObject): PtrInt; overload; /// wrapper to sort the items stored in a T*ObjArray dynamic array // - as expected by TJSONSerializer.RegisterObjArrayForJSON() @@ -6274,7 +6391,7 @@ procedure ObjArraysClear(const aObjArray: array of pointer); {$ifndef DELPHI5OROLDER} /// wrapper to add an item to a T*InterfaceArray dynamic array storage -function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): integer; +function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): PtrInt; /// wrapper to add once an item to a T*InterfaceArray dynamic array storage procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown); @@ -6283,17 +6400,17 @@ procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown); // - search is performed by address/reference, not by content // - return -1 if the item is not found in the dynamic array, or the index of // the matching entry otherwise -function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): integer; +function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): PtrInt; {$ifdef HASINLINE}inline;{$endif} /// wrapper to delete an item in a T*InterfaceArray dynamic array storage // - search is performed by address/reference, not by content // - do nothing if the item is not found in the dynamic array -function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): integer; overload; +function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): PtrInt; overload; /// wrapper to delete an item in a T*InterfaceArray dynamic array storage // - do nothing if the item is not found in the dynamic array -procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: integer); overload; +procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: PtrInt); overload; {$endif DELPHI5OROLDER} @@ -6362,11 +6479,11 @@ procedure AppendShortComma(text: PAnsiChar; len: integer; var result: shortstrin /// fast search of an exact case-insensitive match of a RTTI's PShortString array function FindShortStringListExact(List: PShortString; MaxValue: integer; - aValue: PUTF8Char; aValueLen: integer): integer; + aValue: PUTF8Char; aValueLen: PtrInt): integer; /// fast search of an left-trimmed lowercase match of a RTTI's PShortString array function FindShortStringListTrimLowerCase(List: PShortString; MaxValue: integer; - aValue: PUTF8Char; aValueLen: integer): integer; + aValue: PUTF8Char; aValueLen: PtrInt): integer; /// retrieve the type name from its low-level RTTI function TypeInfoToName(aTypeInfo: pointer): RawUTF8; overload; @@ -6439,19 +6556,38 @@ function GUIDToRawUTF8({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} gui // - this version is faster than the one supplied by SysUtils function GUIDToString({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} guid: TGUID): string; +type + /// low-level object implementing a 32-bit Pierre L'Ecuyer software generator + // - as used by RandomGsl function, and Random32 if no RDRAND hardware is available + // - is not thread-safe, but cross-compiler and cross-platform, still very + // fast with a much better distribution than Delphi system's Random() function + {$ifdef FPC_OR_UNICODE}TLecuyer = record{$else}TLecuyer = object{$endif} + public + rs1, rs2, rs3, seedcount: cardinal; + /// force an immediate seed of the generator from current system state + // - should be called before any call to the Next method + procedure Seed(entropy: PByteArray; entropylen: PtrInt); + /// compute the next 32-bit generated value + // - will automatically reseed after around 65,000 generated values + function Next: cardinal; overload; + /// compute the next 32-bit generated value, in range [0..max-1] + // - will automatically reseed after around 65,000 generated values + function Next(max: cardinal): cardinal; overload; + end; + /// fast compute of some 32-bit random value -// - will use RDRAND Intel x86/x64 opcode if available, or fast gsl_rng_taus2 -// generator by Pierre L'Ecuyer (period is 2^88, i.e. about 10^26) -// - will fast generate some random-like 32-bit output +// - will use (slow but) hardware-derivated RDRAND Intel x86/x64 opcode if +// available, or fast gsl_rng_taus2 generator by Pierre L'Ecuyer (which period +// is 2^88, i.e. about 10^26) if the CPU doesn't support it // - use rather TAESPRNG.Main.FillRandom() for cryptographic-level randomness // - thread-safe function: each thread will maintain its own gsl_rng_taus2 table function Random32: cardinal; overload; /// fast compute of some 32-bit random value, with a maximum (excluded) upper value // - i.e. returns a value in range [0..max-1] -// - will use RDRAND Intel x86/x64 opcode if available, or fast gsl_rng_taus2 -// generator by Pierre L'Ecuyer (period is 2^88, i.e. about 10^26) -// - will fast generate some random-like 32-bit output +// - will use (slow but) hardware-derivated RDRAND Intel x86/x64 opcode if +// available, or fast gsl_rng_taus2 generator by Pierre L'Ecuyer (which period +// is 2^88, i.e. about 10^26) if the CPU doesn't support it // - use rather TAESPRNG.Main.FillRandom() for cryptographic-level randomness // - thread-safe function: each thread will maintain its own gsl_rng_taus2 table function Random32(max: cardinal): cardinal; overload; @@ -6459,8 +6595,8 @@ function Random32(max: cardinal): cardinal; overload; /// fast compute of some 32-bit random value, using the gsl_rng_taus2 generator // - plain Random32 may call RDRAND opcode on Intel CPUs, wherease this function // will use well documented (and proven) Pierre L'Ecuyer software generator -// - may be used if you don't want/trust RDRAND, or expect a well defined -// cross-platform generator +// - may be used if you don't want/trust RDRAND, if you expect a well defined +// cross-platform generator, or have higher performance expectations // - use rather TAESPRNG.Main.FillRandom() for cryptographic-level randomness // - thread-safe function: each thread will maintain its own gsl_rng_taus2 table function Random32gsl: cardinal; overload; @@ -6479,13 +6615,14 @@ function Random32gsl(max: cardinal): cardinal; overload; procedure Random32Seed(entropy: pointer=nil; entropylen: integer=0); /// fill some memory buffer with random values -// - the destination buffer is expected to be allocated as 32 bit items +// - the destination buffer is expected to be allocated as 32-bit items // - use internally crc32c() with some rough entropy source, and Random32 // gsl_rng_taus2 generator or hardware RDRAND Intel x86/x64 opcode if available -// (and forcegsl is kept to its default false value) +// (and ForceGsl is kept to its default false value) // - consider using instead the cryptographic secure TAESPRNG.Main.FillRandom() -// method from the SynCrypto unit -procedure FillRandom(Dest: PCardinalArray; CardinalCount: integer; forcegsl: boolean=false); +// method from the SynCrypto unit - in particular, RDRAND could be slow +// as reported by https://en.wikipedia.org/wiki/RdRand#Performance +procedure FillRandom(Dest: PCardinalArray; CardinalCount: integer; ForceGsl: boolean=false); /// compute a random GUID value procedure RandomGUID(out result: TGUID); overload; @@ -6562,6 +6699,10 @@ function RecordEquals(const RecA, RecB; TypeInfo: pointer; // WinAnsiString, SynUnicode or even RawUnicode/WideString function RecordSave(const Rec; TypeInfo: pointer): RawByteString; overload; +/// save a record content into a TBytes dynamic array +// - could be used as an alternative to RawByteString's RecordSave() +function RecordSaveBytes(const Rec; TypeInfo: pointer): TBytes; + /// save a record content into a destination memory buffer // - Dest must be at least RecordSaveLength() bytes long // - will return the Rec size, in bytes, into Len reference variable @@ -6595,6 +6736,10 @@ function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer; function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer): PAnsiChar; overload; {$ifdef HASINLINE}inline;{$endif} +/// save a record content into a destination memory buffer +// - caller should make Dest.Done once finished with Dest.buf/Dest.len buffer +procedure RecordSave(const Rec; var Dest: TSynTempBuffer; TypeInfo: pointer); overload; + /// save a record content into a Base-64 encoded UTF-8 text content // - will use RecordSave() format, with a left-sided binary CRC function RecordSaveBase64(const Rec; TypeInfo: pointer; UriCompatible: boolean=false): RawUTF8; @@ -6617,15 +6762,18 @@ function RecordSaveJSON(const Rec; TypeInfo: pointer; /// fill a record content from a memory buffer as saved by RecordSave() // - return nil if the Source buffer is incorrect -// - will return the Rec size, in bytes, into Len reference variable // - in case of success, return the memory buffer pointer just after the -// read content +// read content, and set the Rec size, in bytes, into Len reference variable // - will use a proprietary binary format, with some variable-length encoding // of the string length - note that if you change the type definition, any // previously-serialized content will fail, maybe triggering unexpected GPF: you // may use TypeInfoToHash() if you share this binary data accross executables function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer; - Len: PInteger=nil): PAnsiChar; + Len: PInteger=nil): PAnsiChar; overload; + +/// fill a record content from a memory buffer as saved by RecordSave() +// - returns false if the Source buffer was incorrect, true on success +function RecordLoad(var Res; const Source: RawByteString; TypeInfo: pointer): boolean; overload; /// read a record content from a Base-64 encoded content // - expects RecordSaveBase64() format, with a left-sided binary CRC @@ -6683,6 +6831,8 @@ function DynArraySave(var Value; TypeInfo: pointer): RawByteString; // - Value shall be set to the target dynamic array field // - is just a wrapper around TDynArray.LoadFromJSON(), creating a temporary // TDynArray wrapper on the stack +// - return a pointer at the end of the data read from JSON, nil in case +// of an invalid input buffer // - to be used e.g. for custom record JSON unserialization, within a // TDynArrayJSONCustomReader callback // - warning: the JSON buffer will be modified in-place during process - use @@ -6729,6 +6879,7 @@ function DynArrayElementTypeName(TypeInfo: pointer; ElemTypeInfo: PPointer=nil; // - used internally to guess the associated item type name function DynArrayItemTypeLen(const aDynArrayTypeName: RawUTF8): integer; + /// compare two "array of boolean" elements function SortDynArrayBoolean(const A,B): integer; @@ -6746,30 +6897,27 @@ function SortDynArrayWord(const A,B): integer; /// compare two "array of integer" elements function SortDynArrayInteger(const A,B): integer; - {$ifndef CPUX86}{$ifdef HASINLINE}inline;{$endif}{$endif} /// compare two "array of cardinal" elements function SortDynArrayCardinal(const A,B): integer; /// compare two "array of Int64" or "array of Currency" elements function SortDynArrayInt64(const A,B): integer; - {$ifndef CPUX86}{$ifdef HASINLINE}inline;{$endif}{$endif} /// compare two "array of QWord" elements -// - note that QWordA>QWordB is wrong on older versions of Delphi, so you should -// better use this function to properly compare two QWord values over CPUX86 +// - note that QWord(A)>QWord(B) is wrong on older versions of Delphi, so you +// should better use this function or CompareQWord() to properly compare two +// QWord values over CPUX86 function SortDynArrayQWord(const A,B): integer; - {$ifndef CPUX86}{$ifdef HASINLINE}inline;{$endif}{$endif} /// compare two "array of THash128" elements function SortDynArray128(const A,B): integer; - {$ifdef FPC_OR_UNICODE}inline;{$endif} // C2096 Delphi 2007 internal error /// compare two "array of THash256" elements -function SortDynArray256(const A,B): integer; {$ifdef FPC_OR_UNICODE}inline;{$endif} +function SortDynArray256(const A,B): integer; /// compare two "array of THash512" elements -function SortDynArray512(const A,B): integer; {$ifdef FPC_OR_UNICODE}inline;{$endif} +function SortDynArray512(const A,B): integer; /// compare two "array of TObject/pointer" elements function SortDynArrayPointer(const A,B): integer; @@ -6813,16 +6961,15 @@ function SortDynArrayStringI(const A,B): integer; /// compare two "array of TFileName" elements, as file names // - i.e. with no case sensitivity, and grouped by file extension // - the expected string type is the generic RTL string, i.e. TFileName +// - calls internally GetFileNameWithoutExt() and AnsiCompareFileName() function SortDynArrayFileName(const A,B): integer; {$ifndef NOVARIANTS} /// compare two "array of variant" elements, with case sensitivity function SortDynArrayVariant(const A,B): integer; - {$ifdef HASINLINE}inline;{$endif} /// compare two "array of variant" elements, with no case sensitivity function SortDynArrayVariantI(const A,B): integer; - {$ifdef HASINLINE}inline;{$endif} /// compare two "array of variant" elements, with or without case sensitivity function SortDynArrayVariantComp(const A,B: TVarData; caseInsensitive: boolean): integer; @@ -6878,13 +7025,10 @@ function HashByte(const Elem; Hasher: THasher): cardinal; /// hash one Word value - simply return the value ignore Hasher() parameter function HashWord(const Elem; Hasher: THasher): cardinal; -/// hash one Integer value - simply return the value ignore Hasher() parameter +/// hash one Integer/cardinal value - simply return the value ignore Hasher() parameter function HashInteger(const Elem; Hasher: THasher): cardinal; -/// hash one Cardinal value - simply return the value ignore Hasher() parameter -function HashCardinal(const Elem; Hasher: THasher): cardinal; - -/// hash one Int64 value with the suppplied Hasher() function +/// hash one Int64/Qword value with the suppplied Hasher() function function HashInt64(const Elem; Hasher: THasher): cardinal; /// hash one THash128 value with the suppplied Hasher() function @@ -6930,7 +7074,7 @@ function HashPointer(const Elem; Hasher: THasher): cardinal; // - not to be used as such, but e.g. when inlining TDynArray methods DYNARRAY_HASHFIRSTFIELD: array[boolean,TDynArrayKind] of TDynArrayHashOne = ( (nil, HashByte, HashByte, HashWord, HashInteger, - HashCardinal, HashCardinal, HashInt64, HashInt64, HashInt64, + HashInteger, HashInteger, HashInt64, HashInt64, HashInt64, HashInt64, HashInt64, HashInt64, HashInt64, HashAnsiString, HashAnsiString, {$ifdef UNICODE}HashUnicodeString{$else}HashAnsiString{$endif}, @@ -6938,7 +7082,7 @@ function HashPointer(const Elem; Hasher: THasher): cardinal; Hash256, Hash512, HashPointer, {$ifndef NOVARIANTS}HashVariant,{$endif} nil), (nil, HashByte, HashByte, HashWord, HashInteger, - HashCardinal, HashCardinal, HashInt64, HashInt64, HashInt64, + HashInteger, HashInteger, HashInt64, HashInt64, HashInt64, HashInt64, HashInt64, HashInt64, HashInt64, HashAnsiStringI, HashAnsiStringI, {$ifdef UNICODE}HashUnicodeStringI{$else}HashAnsiStringI{$endif}, @@ -7585,7 +7729,7 @@ TTextWriterClass = class of TTextWriter; TTextWriterOptions = set of TTextWriterOption; /// may be used to allocate on stack a 8KB work buffer for a TTextWriter - // - via the CreateOwnedStream overloaded constructor + // - via the TTextWriter.CreateOwnedStream overloaded constructor TTextWriterStackBuffer = array[0..8191] of AnsiChar; /// simple writer to a Stream, specialized for the TEXT format @@ -7595,23 +7739,23 @@ TTextWriter = class protected B, BEnd: PUTF8Char; fStream: TStream; - fInitialStreamPosition: cardinal; - fTotalFileSize: cardinal; + fInitialStreamPosition: PtrUInt; + fTotalFileSize: PtrUInt; fCustomOptions: TTextWriterOptions; // internal temporary buffer fTempBufSize: Integer; fTempBuf: PUTF8Char; - fHumanReadableLevel: integer; - fEchoBuf: RawUTF8; - fEchoStart: integer; - fEchos: array of TOnTextWriterEcho; fOnWriteObject: TOnTextWriterObjectProp; /// used by WriteObjectAsString/AddDynArrayJSONAsString methods fInternalJSONWriter: TTextWriter; - function GetLength: cardinal; + fHumanReadableLevel: integer; + fEchoStart: PtrInt; + fEchoBuf: RawUTF8; + fEchos: array of TOnTextWriterEcho; + function GetTextLength: PtrUInt; procedure SetStream(aStream: TStream); procedure SetBuffer(aBuf: pointer; aBufSize: integer); - function EchoFlush: integer; + function EchoFlush: PtrInt; procedure InternalAddFixedAnsi(Source: PAnsiChar; SourceChars: Cardinal; const AnsiToWide: TWordDynArray; Escape: TTextWriterKind); function GetEndOfLineCRLF: boolean; @@ -7716,10 +7860,10 @@ TTextWriter = class procedure Add(c1,c2: AnsiChar); overload; {$ifdef HASINLINE}inline;{$endif} {$ifndef CPU64} // already implemented by Add(Value: PtrInt) method - /// append a 64 bit signed Integer Value as text + /// append a 64-bit signed Integer Value as text procedure Add(Value: Int64); overload; {$endif} - /// append a 32 bit signed Integer Value as text + /// append a 32-bit signed Integer Value as text procedure Add(Value: PtrInt); overload; /// append a boolean Value as text // - write either 'true' or 'false' @@ -7829,7 +7973,7 @@ TTextWriter = class /// append an Integer Value as a 2 digits String with comma procedure Add2(Value: integer); /// append the current UTC date and time, in a log-friendly format - // - e.g. append '20110325 19241502 ' + // - e.g. append '20110325 19241502' // - you may set LocalTime=TRUE to write the local date and time instead // - this method is very fast, and avoid most calculation or API calls procedure AddCurrentLogTime(LocalTime: boolean); @@ -7843,14 +7987,12 @@ TTextWriter = class procedure AddLine(const Text: shortstring); /// append an UTF-8 String, with no JSON escaping procedure AddString(const Text: RawUTF8); - {$ifdef HASINLINE}inline;{$endif} /// append several UTF-8 strings procedure AddStrings(const Text: array of RawUTF8); overload; /// append an UTF-8 string several times procedure AddStrings(const Text: RawUTF8; count: integer); overload; /// append a ShortString procedure AddShort(const Text: ShortString); - {$ifdef HASINLINE}inline;{$endif} /// append a sub-part of an UTF-8 String // - emulates AddString(copy(Text,start,len)) procedure AddStringCopy(const Text: RawUTF8; start,len: integer); @@ -7884,7 +8026,7 @@ TTextWriter = class // - Instance must be not nil // - overriden version in TJSONSerializer would implement IncludeUnitName procedure AddInstancePointer(Instance: TObject; SepChar: AnsiChar; - IncludeUnitName: boolean); virtual; + IncludeUnitName, IncludePointer: boolean); virtual; /// append a quoted string as JSON, with in-place decoding // - if QuotedString does not start with ' or ", it will written directly // (i.e. expects to be a number, or null/true/false constants) @@ -7905,13 +8047,17 @@ TTextWriter = class /// write some record content as binary, Base64 encoded with our magic prefix procedure WrRecord(const Rec; TypeInfo: pointer); /// write some #0 ended UTF-8 text, according to the specified format + // - if Escape is a constant, consider calling directly AddNoJSONEscape, + // AddJSONEscape or AddOnSameLine methods procedure Add(P: PUTF8Char; Escape: TTextWriterKind); overload; /// write some #0 ended UTF-8 text, according to the specified format + // - if Escape is a constant, consider calling directly AddNoJSONEscape, + // AddJSONEscape or AddOnSameLine methods procedure Add(P: PUTF8Char; Len: PtrInt; Escape: TTextWriterKind); overload; - {$ifdef HASINLINE}inline;{$endif} /// write some #0 ended Unicode text as UTF-8, according to the specified format + // - if Escape is a constant, consider calling directly AddNoJSONEscapeW, + // AddJSONEscapeW or AddOnSameLineW methods procedure AddW(P: PWord; Len: PtrInt; Escape: TTextWriterKind); - {$ifdef HASINLINE}inline;{$endif} /// append some UTF-8 encoded chars to the buffer, from the main AnsiString type // - use the current system code page for AnsiString parameter procedure AddAnsiString(const s: AnsiString; Escape: TTextWriterKind); overload; @@ -7990,8 +8136,12 @@ TTextWriter = class /// fast conversion from binary data into quoted MSB lowercase hexa chars // - up to the internal buffer bytes may be converted procedure AddBinToHexDisplayQuoted(Bin: pointer; BinBytes: integer); - /// add the pointer into hexa chars, ready to be displayed - procedure AddPointer(P: PtrUInt); + /// append a Value as significant hexadecimal text + // - append its minimal size, i.e. excluding highest bytes containing 0 + // - use GetNextItemHexa() to decode such a text value + procedure AddBinToHexDisplayMinChars(Bin: pointer; BinBytes: PtrInt); + /// add the pointer into significant hexa chars, ready to be displayed + procedure AddPointer(P: PtrUInt); {$ifdef HASINLINE}inline;{$endif} /// write a byte as hexa chars procedure AddByteToHex(Value: byte); /// write a Int18 value (0..262143) as 3 chars @@ -8010,8 +8160,7 @@ TTextWriter = class /// append some UTF-8 encoded chars to the buffer, from a generic string type // - faster than AddJSONEscape(pointer(StringToUTF8(string)) // - escapes chars according to the JSON RFC - procedure AddJSONEscapeString(const s: string); - {$ifdef HASINLINE}inline;{$endif} + procedure AddJSONEscapeString(const s: string); {$ifdef HASINLINE}inline;{$endif} /// append some UTF-8 encoded chars to the buffer, from the main AnsiString type // - escapes chars according to the JSON RFC procedure AddJSONEscapeAnsiString(const s: AnsiString); @@ -8019,8 +8168,7 @@ TTextWriter = class // - faster than AddNoJSONEscape(pointer(StringToUTF8(string)) // - don't escapes chars according to the JSON RFC // - will convert the Unicode chars into UTF-8 - procedure AddNoJSONEscapeString(const s: string); - {$ifdef UNICODE}inline;{$endif} + procedure AddNoJSONEscapeString(const s: string); {$ifdef UNICODE}inline;{$endif} /// append some Unicode encoded chars to the buffer // - if Len is 0, Len is calculated from zero-ended widechar // - escapes chars according to the JSON RFC @@ -8337,7 +8485,7 @@ TTextWriter = class /// how many bytes were currently written on disk // - excluding the bytes in the internal buffer // - see TextLength for the total number of bytes, on both disk and memory - property WrittenBytes: cardinal read fTotalFileSize; + property WrittenBytes: PtrUInt read fTotalFileSize; /// the last char appended is canceled procedure CancelLastChar; overload; {$ifdef HASINLINE}inline;{$endif} @@ -8355,7 +8503,7 @@ TTextWriter = class /// count of added bytes to the stream // - see PendingBytes for the number of bytes currently in the memory buffer // or WrittenBytes for the number of bytes already written to disk - property TextLength: cardinal read GetLength; + property TextLength: PtrUInt read GetTextLength; /// define how AddEndOfLine method stores its line feed characters // - by default (FALSE), it will append a LF (#10) char to the buffer // - you can set this property to TRUE, so that CR+LF (#13#10) chars will @@ -8392,129 +8540,6 @@ function SaveJSON(const Value; TypeInfo: pointer; EnumSetsAsText: boolean=false): RawUTF8; overload; {$ifdef HASINLINE}inline;{$endif} -type - /// abstract TSynPersistent class allowing safe storage of a password - // - the associated Password, e.g. for storage or transmission encryption - // will be persisted encrypted with a private key (which can be customized) - // - if default simple symmetric encryption is not enough, you may define - // a custom TSynPersistentWithPasswordUserCrypt callback, e.g. to - // SynCrypto's CryptDataForCurrentUser, for hardened password storage - // - a published property should be defined as such in inherited class: - // ! property PasswordPropertyName: RawUTF8 read fPassword write fPassword; - // - use the PassWordPlain property to access to its uncyphered value - TSynPersistentWithPassword = class(TSynPersistent) - protected - fPassWord: RawUTF8; - fKey: cardinal; - function GetKey: cardinal; - {$ifdef HASINLINE}inline;{$endif} - function GetPassWordPlain: RawUTF8; - function GetPassWordPlainInternal(AppSecret: RawUTF8): RawUTF8; - procedure SetPassWordPlain(const Value: RawUTF8); - public - /// finalize the instance - destructor Destroy; override; - /// this class method could be used to compute the encrypted password, - // ready to be stored as JSON, according to a given private key - class function ComputePassword(const PlainPassword: RawUTF8; - CustomKey: cardinal=0): RawUTF8; overload; - /// this class method could be used to compute the encrypted password from - // a binary digest, ready to be stored as JSON, according to a given private key - // - just a wrapper around ComputePassword(BinToBase64URI()) - class function ComputePassword(PlainPassword: pointer; PlainPasswordLen: integer; - CustomKey: cardinal=0): RawUTF8; overload; - /// this class method could be used to decrypt a password, stored as JSON, - // according to a given private key - // - may trigger a ESynException if the password was stored using a custom - // TSynPersistentWithPasswordUserCrypt callback, and the current user - // doesn't match the expected user stored in the field - class function ComputePlainPassword(const CypheredPassword: RawUTF8; - CustomKey: cardinal=0; const AppSecret: RawUTF8=''): RawUTF8; - /// low-level function used to identify if a given field is a Password - // - this method is used e.g. by TJSONSerializer.WriteObject to identify the - // password field, since its published name is set by the inherited classes - function GetPasswordFieldAddress: pointer; - {$ifdef HASINLINE}inline;{$endif} - /// the private key used to cypher the password storage on serialization - // - application can override the default 0 value at runtime - property Key: cardinal read GetKey write fKey; - /// access to the associated unencrypted Password value - // - read may trigger a ESynException if the password was stored using a - // custom TSynPersistentWithPasswordUserCrypt callback, and the current user - // doesn't match the expected user stored in the field - property PasswordPlain: RawUTF8 read GetPassWordPlain write SetPassWordPlain; - end; - - /// could be used to store a credential pair, as user name and password - // - password will be stored with TSynPersistentWithPassword encryption - TSynUserPassword = class(TSynPersistentWithPassword) - protected - fUserName: RawUTF8; - published - /// the associated user name - property UserName: RawUTF8 read FUserName write FUserName; - /// the associated encrypted password - // - use the PasswordPlain public property to access to the uncrypted password - property Password: RawUTF8 read FPassword write FPassword; - end; - - /// handle safe storage of any connection properties - // - would be used by SynDB.pas to serialize TSQLDBConnectionProperties, or - // by mORMot.pas to serialize TSQLRest instances - // - the password will be stored as Base64, after a simple encryption as - // defined by TSynPersistentWithPassword - // - typical content could be: - // $ { - // $ "Kind": "TSQLDBSQLite3ConnectionProperties", - // $ "ServerName": "server", - // $ "DatabaseName": "", - // $ "User": "", - // $ "Password": "PtvlPA==" - // $ } - // - the "Kind" value will be used to let the corresponding TSQLRest or - // TSQLDBConnectionProperties NewInstance*() class methods create the - // actual instance, from its class name - TSynConnectionDefinition = class(TSynPersistentWithPassword) - protected - fKind: string; - fServerName: RawUTF8; - fDatabaseName: RawUTF8; - fUser: RawUTF8; - public - /// unserialize the database definition from JSON - // - as previously serialized with the SaveToJSON method - // - you can specify a custom Key used for password encryption, if the - // default value is not safe enough for you - // - this method won't use JSONToObject() so avoid any dependency to mORMot.pas - constructor CreateFromJSON(const JSON: RawUTF8; Key: cardinal=0); virtual; - /// serialize the database definition as JSON - // - this method won't use ObjectToJSON() so avoid any dependency to mORMot.pas - function SaveToJSON: RawUTF8; virtual; - published - /// the class name implementing the connection or TSQLRest instance - // - will be used to instantiate the expected class type - property Kind: string read fKind write fKind; - /// the associated server name (or file, for SQLite3) to be connected to - property ServerName: RawUTF8 read fServerName write fServerName; - /// the associated database name (if any), or additional options - property DatabaseName: RawUTF8 read fDatabaseName write fDatabaseName; - /// the associated User Identifier (if any) - property User: RawUTF8 read fUser write fUser; - /// the associated Password, e.g. for storage or transmission encryption - // - will be persisted encrypted with a private key - // - use the PassWordPlain property to access to its uncyphered value - property Password: RawUTF8 read fPassword write fPassword; - end; - -var - /// function prototype to customize TSynPersistent class password storage - // - is called when 'user1:base64pass1,user2:base64pass2' layout is found, - // and the current user logged on the system is user1 or user2 - // - you should not call this low-level method, but assign e.g. from SynCrypto: - // $ TSynPersistentWithPasswordUserCrypt := CryptDataForCurrentUser; - TSynPersistentWithPasswordUserCrypt: - function(const Data,AppServer: RawByteString; Encrypt: boolean): RawByteString; - /// will serialize any TObject into its UTF-8 JSON representation /// - serialize as JSON the published integer, Int64, floating point values, // TDateTime (stored as ISO 8601 text), string, variant and enumerate @@ -8545,7 +8570,7 @@ function ObjectsToJSON(const Names: array of RawUTF8; const Values: array of TOb // - internally make use of an efficient hashing algorithm for fast response // (i.e. TSynNameValue will use the TDynArrayHashed wrapper mechanism) // - this class is thread-safe if you use properly the associated Safe lock - TSynCache = class + TSynCache = class(TSynPersistentLock) protected /// last index in fNameValue.List[] if was added by Find() // - contains -1 if no previous immediate call to Find() @@ -8556,7 +8581,6 @@ TSynCache = class fMaxRamUsed: cardinal; fTimeoutSeconds: cardinal; fTimeoutTix: cardinal; - fSafe: TSynLocker; procedure ResetIfNeeded; public /// initialize the internal storage @@ -8567,9 +8591,7 @@ TSynCache = class // - by default, there is no timeout period, but you may specify a number of // seconds of inactivity (i.e. no Add call) after which the cache is flushed constructor Create(aMaxCacheRamUsed: cardinal=16 shl 20; - aCaseSensitive: boolean=false; aTimeoutSeconds: cardinal=0); - /// finalize the internal storage - destructor Destroy; override; + aCaseSensitive: boolean=false; aTimeoutSeconds: cardinal=0); reintroduce; /// find a Key in the cache entries // - return '' if nothing found: you may call Add() just after to insert // the expected value in the cache @@ -8605,7 +8627,7 @@ TSynCache = class // ! finally // ! cache.Safe.Unlock; // ! end; - property Safe: TSynLocker read fSafe; + property Safe: PSynLocker read fSafe; /// the current global size of Values in RAM cache, in bytes property RamUsed: cardinal read fRamUsed; /// the maximum RAM to be used for values, in bytes @@ -9139,11 +9161,11 @@ TAlgoCompress = class(TSynPersistent) {$ifdef HASINLINE}inline;{$endif} /// compress a memory buffer with crc32c hashing to a RawByteString function Compress(const Plain: RawByteString; CompressionSizeTrigger: integer=100; - CheckMagicForCompressed: boolean=false): RawByteString; overload; + CheckMagicForCompressed: boolean=false; BufferOffset: integer=0): RawByteString; overload; {$ifdef HASINLINE}inline;{$endif} /// compress a memory buffer with crc32c hashing to a RawByteString function Compress(Plain: PAnsiChar; PlainLen: integer; CompressionSizeTrigger: integer=100; - CheckMagicForCompressed: boolean=false): RawByteString; overload; + CheckMagicForCompressed: boolean=false; BufferOffset: integer=0): RawByteString; overload; /// compress a memory buffer with crc32c hashing // - supplied Comp buffer should contain at least CompressDestLen(PlainLen) bytes function Compress(Plain, Comp: PAnsiChar; PlainLen, CompLen: integer; @@ -9156,7 +9178,8 @@ TAlgoCompress = class(TSynPersistent) function CompressToBytes(Plain: PAnsiChar; PlainLen: integer; CompressionSizeTrigger: integer=100; CheckMagicForCompressed: boolean=false): TByteDynArray; overload; /// uncompress a RawByteString memory buffer with crc32c hashing - function Decompress(const Comp: RawByteString; Load: TAlgoCompressLoad=aclNormal): RawByteString; overload; + function Decompress(const Comp: RawByteString; Load: TAlgoCompressLoad=aclNormal; + BufferOffset: integer=0): RawByteString; overload; {$ifdef HASINLINE}inline;{$endif} /// uncompress a RawByteString memory buffer with crc32c hashing // - returns TRUE on success @@ -9164,7 +9187,7 @@ TAlgoCompress = class(TSynPersistent) Load: TAlgoCompressLoad=aclNormal): boolean; /// uncompress a memory buffer with crc32c hashing procedure Decompress(Comp: PAnsiChar; CompLen: integer; out Result: RawByteString; - Load: TAlgoCompressLoad=aclNormal); overload; + Load: TAlgoCompressLoad=aclNormal; BufferOffset: integer=0); overload; /// uncompress a RawByteString memory buffer with crc32c hashing function Decompress(const Comp: TByteDynArray): RawByteString; overload; {$ifdef HASINLINE}inline;{$endif} @@ -9211,6 +9234,11 @@ TAlgoCompress = class(TSynPersistent) /// get the TAlgoCompress instance corresponding to the AlgoID stored // in the supplied compressed buffer // - returns nil if no algorithm was identified + // - also identifies "stored" content in IsStored variable + class function Algo(Comp: PAnsiChar; CompLen: integer; out IsStored: boolean): TAlgoCompress; overload; + /// get the TAlgoCompress instance corresponding to the AlgoID stored + // in the supplied compressed buffer + // - returns nil if no algorithm was identified class function Algo(const Comp: RawByteString): TAlgoCompress; overload; {$ifdef HASINLINE}inline;{$endif} /// get the TAlgoCompress instance corresponding to the AlgoID stored @@ -9222,6 +9250,11 @@ TAlgoCompress = class(TSynPersistent) // - returns nil if no algorithm was identified // - stored content is identified as TAlgoSynLZ class function Algo(AlgoID: byte): TAlgoCompress; overload; + /// quickly validate a compressed buffer content, without uncompression + // - extract the TAlgoCompress, and call DecompressHeader() to check the + // hash of the compressed data, and return then uncompressed size + // - returns 0 on error (e.g. unknown algorithm or incorrect hash) + class function UncompressedSize(const Comp: RawByteString): integer; /// returns the algorithm name, from its classname // - e.g. TAlgoSynLZ->'synlz' TAlgoLizard->'lizard' nil->'none' function AlgoName: TShort16; @@ -9293,7 +9326,7 @@ TAlgoCompressWithNoDestLen = class(TAlgoCompress) // - TDynArray is a wrapper which do not store anything, whereas this class // is able to store both keys and values, and provide convenient methods to // access the stored data, including JSON serialization and binary storage - TSynDictionary = class(TSynPersistentLocked) + TSynDictionary = class(TSynPersistentLock) protected fKeys: TDynArrayHashed; fValues: TDynArray; @@ -9417,6 +9450,14 @@ TSynDictionary = class(TSynPersistentLocked) // were not found // - this method is thread-safe, since it will lock the instance function FindInArray(const aKey, aArrayValue): boolean; + /// search of a stored key by its associated key, and return a key local copy + // - won't use any hashed index but TDynArray.IndexOf over fValues, + // so is much slower than FindAndCopy() + // - will update the associated timeout value of the entry, unless + // aUpdateTimeOut is set to false + // - so this method is thread-safe + // - returns TRUE if aValue was found, FALSE if no match exists + function FindKeyFromValue(const aValue; out aKey; aUpdateTimeOut: boolean=true): boolean; /// add aArrayValue item within a dynamic-array value associated via aKey // - expect the stored value to be a dynamic array itself // - would search for aKey as primary key, then use TDynArray.Add @@ -9451,8 +9492,9 @@ TSynDictionary = class(TSynPersistentLocked) {$ifndef DELPHI5OROLDER} /// make a copy of the stored values // - this method is thread-safe, since it will lock the instance during copy + // - resulting length(Dest) will match the exact values count // - T*ObjArray will be reallocated and copied by content (using a temporary - // JSON serialization), unless ObjArrayByRef is true + // JSON serialization), unless ObjArrayByRef is true and pointers are copied procedure CopyValues(out Dest; ObjArrayByRef: boolean=false); {$endif DELPHI5OROLDER} /// serialize the content as a "key":value JSON object @@ -9480,7 +9522,7 @@ TSynDictionary = class(TSynPersistentLocked) function LoadFromBinary(const binary: RawByteString): boolean; /// can be assigned to OnCanDeleteDeprecated to check TSynPersistentLock(aValue).Safe.IsLocked class function OnCanDeleteSynPersistentLock(const aKey, aValue; aIndex: integer): boolean; - /// can be assigned to OnCanDeleteDeprecated to check TSynPersistentLocked(aValue).Safe.IsLocked + /// can be assigned to OnCanDeleteDeprecated to check TSynPersistentLock(aValue).Safe.IsLocked class function OnCanDeleteSynPersistentLocked(const aKey, aValue; aIndex: integer): boolean; /// returns how many items are currently stored in this dictionary // - this method is thread-safe @@ -9512,19 +9554,23 @@ TSynDictionary = class(TSynPersistentLocked) /// thread-safe FIFO (First-In-First-Out) in-order queue of records // - uses internally a dynamic array storage, with a sliding algorithm // (more efficient than the FPC or Delphi TQueue) - TSynQueue = class(TSynPersistentLocked) + TSynQueue = class(TSynPersistentLock) protected fValues: TDynArray; fValueVar: pointer; fCount, fFirst, fLast: integer; + fWaitPopFlags: set of (wpfDestroying); + fWaitPopCounter: integer; procedure InternalGrow; + function InternalDestroying(incPopCounter: integer): boolean; + function InternalWaitDone(endtix: Int64; const idle: TThreadMethod): boolean; public /// initialize the queue storage // - aTypeInfo should be a dynamic array TypeInfo() RTTI pointer, which // would store the values within this TSynQueue instance constructor Create(aTypeInfo: pointer); reintroduce; virtual; /// finalize the storage - // - would release all internal stored values + // - would release all internal stored values, and call WaitPopFinalize destructor Destroy; override; /// store one item into the queue // - this method is thread-safe, since it will lock the instance @@ -9541,7 +9587,28 @@ TSynQueue = class(TSynPersistentLocked) // - returns false if the queue is empty // - this method is thread-safe, since it will lock the instance function Peek(out aValue): boolean; + /// waiting extract of one item from the queue, as FIFO (First-In-First-Out) + // - returns true if aValue has been filled with a pending item within the + // specified aTimeoutMS time + // - returns false if nothing was pushed into the queue in time, or if + // WaitPopFinalize has been called + // - aWhenIdle could be assigned e.g. to VCL/LCL Application.ProcessMessages + // - this method is thread-safe, but will lock the instance only if needed + function WaitPop(aTimeoutMS: integer; const aWhenIdle: TThreadMethod; out aValue): boolean; + /// waiting lookup of one item from the queue, as FIFO (First-In-First-Out) + // - returns a pointer to a pending item within the specified aTimeoutMS + // time - the Safe.Lock is still there, so that caller could check its content, + // then call Pop() if it is the expected one, and eventually always call Safe.Unlock + // - returns nil if nothing was pushed into the queue in time + // - this method is thread-safe, but will lock the instance only if needed + function WaitPeekLocked(aTimeoutMS: integer; const aWhenIdle: TThreadMethod): pointer; + /// ensure any pending or future WaitPop() returns immediately as false + // - is always called by Destroy destructor + // - could be also called e.g. from an UI OnClose event to avoid any lock + // - this method is thread-safe, but will lock the instance only if needed + procedure WaitPopFinalize; /// delete all items currently stored in this queue, and void its capacity + // - this method is thread-safe, since it will lock the instance procedure Clear; /// initialize a dynamic array with the stored queue items // - aDynArrayValues should be a variable defined as aTypeInfo from Create @@ -9575,8 +9642,8 @@ TSynQueue = class(TSynPersistentLocked) type /// handle memory mapping of a file content - {$ifdef UNICODE}TMemoryMap = record{$else}TMemoryMap = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TMemoryMap = record private + {$else}TMemoryMap = object protected{$endif} fBuf: PAnsiChar; fBufSize: PtrUInt; fFile: THandle; @@ -9921,7 +9988,9 @@ TFileBufferWriter = class // ! TFileBufferWriter.Create(TRawByteStringStream) // - if algo is left to its default nil, will use global AlgoSynLZ // - features direct compression from internal buffer, if stream was not used - function FlushAndCompress(nocompression: boolean=false; algo: TAlgoCompress=nil): RawByteString; + // - BufferOffset could be set to reserve some bytes before the compressed buffer + function FlushAndCompress(nocompression: boolean=false; algo: TAlgoCompress=nil; + BufferOffset: integer=0): RawByteString; /// rewind the Stream to the position when Create() was called // - note that this does not clear the Stream content itself, just // move back its writing position to its initial place @@ -9939,7 +10008,7 @@ TFileBufferWriter = class /// this structure can be used to speed up reading from a file // - use internaly memory mapped files for a file up to 2 GB (Windows has // problems with memory mapped files bigger than this size limit - at least - // with 32 bit executables) - but sometimes, Windows fails to allocate + // with 32-bit executables) - but sometimes, Windows fails to allocate // more than 512 MB for a memory map, because it does lack of contiguous // memory space: in this case, we fall back on direct file reading // - maximum handled file size has no limit (but will use slower direct @@ -9949,8 +10018,8 @@ TFileBufferWriter = class // - is defined either as an object either as a record, due to a bug // in Delphi 2009/2010 compiler (at least): this structure is not initialized // if defined as an object on the stack, but will be as a record :( - {$ifdef UNICODE}TFileBufferReader = record{$else}TFileBufferReader = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TFileBufferReader = record private + {$else}TFileBufferReader = object protected{$endif} fCurrentPos: PtrUInt; fMap: TMemoryMap; /// get Isize + buffer from current memory map or fBufTemp into (P,PEnd) @@ -10052,111 +10121,6 @@ {$ifdef UNICODE}TFileBufferReader = record{$else}TFileBufferReader = object{$e property MappedBuffer: PAnsiChar read fMap.fBuf; end; - /// safe decoding of a TFileBufferWriter content - // - similar to TFileBufferReader, but faster and only for in-memory buffer - // - is also safer, since will check for reaching end of buffer - // - raise a EFastReader exception on decoding error (e.g. if a buffer - // overflow may occur) or call OnErrorOverflow/OnErrorData event handlers - {$ifdef UNICODE}TFastReader = record{$else}TFastReader = object{$endif} - public - /// the current position in the memory - P: PAnsiChar; - /// the last position in the buffer - Last: PAnsiChar; - /// use this event to customize the ErrorOverflow process - OnErrorOverflow: procedure of object; - /// use this event to customize the ErrorData process - OnErrorData: procedure(const fmt: RawUTF8; const args: array of const) of object; - /// some opaque value, which may be a version number to define the binary layout - Tag: PtrInt; - /// initialize the reader from a memory block - procedure Init(Buffer: pointer; Len: integer); overload; - /// initialize the reader from a RawByteString content - procedure Init(const Buffer: RawByteString); overload; - /// raise a EFastReader with an "overflow" error message - procedure ErrorOverflow; - /// raise a EFastReader with an "incorrect data" error message - procedure ErrorData(const fmt: RawUTF8; const args: array of const); - /// read the next 32-bit signed value from the buffer - function VarInt32: integer; {$ifdef HASINLINE}inline;{$endif} - /// read the next 32-bit unsigned value from the buffer - function VarUInt32: cardinal; - /// try to read the next 32-bit signed value from the buffer - // - don't change the current position - function PeekVarInt32(out value: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} - /// try to read the next 32-bit unsigned value from the buffer - // - don't change the current position - function PeekVarUInt32(out value: PtrUInt): boolean; - /// read the next 32-bit unsigned value from the buffer - // - this version won't call ErrorOverflow, but return false on error - // - returns true on read success - function VarUInt32Safe(out Value: cardinal): boolean; - /// read the next 64-bit signed value from the buffer - function VarInt64: Int64; {$ifdef HASINLINE}inline;{$endif} - /// read the next 64-bit unsigned value from the buffer - function VarUInt64: QWord; - /// read the next RawUTF8 value from the buffer - function VarUTF8: RawUTF8; overload; - /// read the next RawUTF8 value from the buffer - procedure VarUTF8(out result: RawUTF8); overload; - /// read the next RawUTF8 value from the buffer - // - this version won't call ErrorOverflow, but return false on error - // - returns true on read success - function VarUTF8Safe(out Value: RawUTF8): boolean; - /// read the next RawByteString value from the buffer - function VarString: RawByteString; {$ifdef HASINLINE}inline;{$endif} - /// read the next pointer and length value from the buffer - procedure VarBlob(out result: TValueResult); overload; {$ifdef HASINLINE}inline;{$endif} - /// read the next pointer and length value from the buffer - function VarBlob: TValueResult; overload; {$ifdef HASINLINE}inline;{$endif} - /// read the next ShortString value from the buffer - function VarShortString: shortstring; {$ifdef HASINLINE}inline;{$endif} - /// fast ignore the next VarUInt32/VarInt32/VarUInt64/VarInt64 value - // - don't raise any exception, so caller could check explicitly for any EOF - procedure VarNextInt; overload; {$ifdef HASINLINE}inline;{$endif} - /// fast ignore the next count VarUInt32/VarInt32/VarUInt64/VarInt64 values - // - don't raise any exception, so caller could check explicitly for any EOF - procedure VarNextInt(count: integer); overload; - /// read the next byte from the buffer - function NextByte: byte; {$ifdef HASINLINE}inline;{$endif} - /// read the next 4 bytes from the buffer as a 32-bit unsigned value - function Next4: cardinal; {$ifdef HASINLINE}inline;{$endif} - /// read the next 8 bytes from the buffer as a 64-bit unsigned value - function Next8: Qword; {$ifdef HASINLINE}inline;{$endif} - /// consumes the next byte from the buffer, if matches a given value - function NextByteEquals(Value: byte): boolean; {$ifdef HASINLINE}inline;{$endif} - /// returns the current position, and move ahead the specified bytes - function Next(DataLen: PtrInt): pointer; {$ifdef HASINLINE}inline;{$endif} - /// returns the current position, and move ahead the specified bytes - function NextSafe(out Data: Pointer; DataLen: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} - {$ifndef NOVARIANTS} - /// read the next variant from the buffer - // - is a wrapper around VariantLoad(), so may suffer from buffer overflow - procedure NextVariant(var Value: variant; CustomVariantOptions: pointer); - /// read the JSON-serialized TDocVariant from the buffer - // - matches TFileBufferWriter.WriteDocVariantData format - procedure NextDocVariantData(out Value: variant; CustomVariantOptions: pointer); - {$ifdef FPC}inline;{$endif} - {$endif NOVARIANTS} - /// copy data from the current position, and move ahead the specified bytes - procedure Copy(out Dest; DataLen: PtrInt); {$ifdef HASINLINE}inline;{$endif} - /// copy data from the current position, and move ahead the specified bytes - // - this version won't call ErrorOverflow, but return false on error - // - returns true on read success - function CopySafe(out Dest; DataLen: PtrInt): boolean; - /// apply TDynArray.LoadFrom on the buffer - // - will unserialize a previously appended dynamic array, e.g. as - // ! aWriter.WriteDynArray(DA); - procedure Read(var DA: TDynArray; NoCheckHash: boolean=false); - /// retrieved cardinal values encoded with TFileBufferWriter.WriteVarUInt32Array - // - only supports wkUInt32, wkVarInt32, wkVarUInt32 kind of encoding - function ReadVarUInt32Array(var Values: TIntegerDynArray): PtrInt; - /// returns TRUE if the current position is the end of the input stream - function EOF: boolean; {$ifdef HASINLINE}inline;{$endif} - /// returns remaining length (difference between Last and P) - function RemainingLength: PtrUInt; {$ifdef HASINLINE}inline;{$endif} - end; - /// FileSeek() overloaded function, working with huge files // - Delphi FileSeek() is buggy -> use this function to safe access files > 2 GB // (thanks to sanyin for the report) @@ -10231,15 +10195,23 @@ procedure JSONEncodeNameSQLValue(const Name,SQLValue: RawUTF8; var result: RawUT type /// points to one value of raw UTF-8 content, decoded from a JSON buffer // - used e.g. by JSONDecode() overloaded function to returns names/values - {$ifdef UNICODE}TValuePUTF8Char = record{$else}TValuePUTF8Char = object{$endif} + {$ifdef FPC_OR_UNICODE}TValuePUTF8Char = record{$else}TValuePUTF8Char = object{$endif} public + /// a pointer to the actual UTF-8 text Value: PUTF8Char; + /// how many UTF-8 bytes are stored in Value ValueLen: PtrInt; + /// convert the value into a UTF-8 string procedure ToUTF8(var Text: RawUTF8); overload; {$ifdef HASINLINE}inline;{$endif} + /// convert the value into a UTF-8 string function ToUTF8: RawUTF8; overload; {$ifdef HASINLINE}inline;{$endif} + /// convert the value into a VCL/generic string function ToString: string; + /// convert the value into a signed integer function ToInteger: PtrInt; {$ifdef HASINLINE}inline;{$endif} + /// convert the value into an unsigned integer function ToCardinal: PtrUInt; {$ifdef HASINLINE}inline;{$endif} + /// will call IdemPropNameU() over the stored text Value function Idem(const Text: RawUTF8): boolean; {$ifdef HASINLINE}inline;{$endif} end; /// used e.g. by JSONDecode() overloaded function to returns values @@ -10249,9 +10221,13 @@ {$ifdef UNICODE}TValuePUTF8Char = record{$else}TValuePUTF8Char = object{$endif /// store one name/value pair of raw UTF-8 content, from a JSON buffer // - used e.g. by JSONDecode() overloaded function to returns names/values TNameValuePUTF8Char = record + /// a pointer to the actual UTF-8 name text Name: PUTF8Char; + /// a pointer to the actual UTF-8 value text Value: PUTF8Char; + /// how many UTF-8 bytes are stored in Name NameLen: integer; + /// how many UTF-8 bytes are stored in Value ValueLen: integer; end; /// used e.g. by JSONDecode() overloaded function to returns name/value pairs @@ -10896,7 +10872,7 @@ procedure BinToHexLower(Bin: PAnsiChar; BinBytes: integer; var result: RawUTF8); // enough space for at least BinBytes*2 chars // - using this function with Bin^ as an integer value will encode it // in big-endian order (most-signignifican byte first): use it for display -procedure BinToHexDisplayLower(Bin, Hex: PAnsiChar; BinBytes: integer); overload; +procedure BinToHexDisplayLower(Bin, Hex: PAnsiChar; BinBytes: PtrInt); overload; /// fast conversion from binary data into lowercase hexa chars function BinToHexDisplayLower(Bin: PAnsiChar; BinBytes: integer): RawUTF8; overload; @@ -10940,6 +10916,14 @@ function LogEscape(source: PAnsiChar; sourcelen: integer; var temp: TLogEscape; enabled: boolean=true): PAnsiChar; {$ifdef HASINLINE}inline;{$endif} +/// returns a text buffer with the (hexadecimal) chars of the input binary +// - is much slower than LogEscape/EscapeToShort, but has no size limitation +function LogEscapeFull(source: PAnsiChar; sourcelen: integer): RawUTF8; overload; + +/// returns a text buffer with the (hexadecimal) chars of the input binary +// - is much slower than LogEscape/EscapeToShort, but has no size limitation +function LogEscapeFull(const source: RawByteString): RawUTF8; overload; + /// fill a shortstring with the (hexadecimal) chars of the input text/binary function EscapeToShort(source: PAnsiChar; sourcelen: integer): shortstring; overload; @@ -11191,6 +11175,12 @@ function Base64uriToBin(sp: PAnsiChar; len: PtrInt): RawByteString; overload; // unsignificant characters, and replace '+' or '/' by '_' or '-' procedure Base64uriToBin(sp: PAnsiChar; len: PtrInt; var result: RawByteString); overload; +/// fast conversion from Base64-URI encoded text into binary data +// - caller should always execute temp.Done when finished with the data +// - in comparison to Base64 standard encoding, will trim any right-sided '=' +// unsignificant characters, and replace '+' or '/' by '_' or '-' +function Base64uriToBin(sp: PAnsiChar; len: PtrInt; var temp: TSynTempBuffer): boolean; overload; + /// fast conversion from Base64-URI encoded text into binary data // - in comparison to Base64 standard encoding, will trim any right-sided '=' // unsignificant characters, and replace '+' or '/' by '_' or '-' @@ -11211,6 +11201,7 @@ function Base64uriToBin(const base64: RawByteString; bin: PAnsiChar; binlen: Ptr {$ifdef HASINLINE}inline;{$endif} /// direct low-level decoding of a Base64-URI encoded buffer +// - the buffer is expected to be at least Base64uriToBinLength() bytes long // - returns true if the supplied sp[] buffer has been successfully decoded // into rp[] - will break at any invalid character, so is always safe to use // - in comparison to Base64 standard encoding, will trim any right-sided '=' @@ -11298,22 +11289,46 @@ function UInt2DigitsToShortFast(Value: byte): TShort4; /// compute CRC16-CCITT checkum on the supplied buffer -// - i.e. 16-bit CRC-CCITT, with polynomial x^16 + x^12 + x^5 + 1 ($1021) and -// $ffff as initial value +// - i.e. 16-bit CRC-CCITT, with polynomial x^16 + x^12 + x^5 + 1 ($1021) +// and $ffff as initial value // - this version is not optimized for speed, but for correctness function crc16(Data: PAnsiChar; Len: integer): cardinal; -// our custom hash function, specialized for Text comparaison -// - has less colision than Adler32 for short strings -// - is faster than CRC32 or Adler32, since use DQWord (128 bytes) aligned read: -// Hash32() is 2.5 GB/s, kr32() 0.9 GB/s, crc32c() 1.7 GB/s or 4.3 GB/s (SSE4.2) +// our custom hash/checksum function, specialized for Text comparaison +// - it is a checksum algorithm, not a hash function: has less colision than +// Adler32 for short strings, but more than xxhash32 or crc32/crc32c +// - written in simple plain pascal, with no L1 CPU cache pollution // - overloaded version for direct binary content hashing -function Hash32(Data: pointer; Len: integer): cardinal; overload; - -// our custom hash function, specialized for Text comparaison -// - has less colision than Adler32 for short strings -// - is faster than CRC32 or Adler32, since use DQWord (128 bytes) aligned read -// - uses RawByteString for binary content hashing, whatever the codepage is +// - crc32c() has less collision - but is faster only on a SSE4.2 x86_64 CPU; +// some numbers on FPC/Linux64, with a SSE4.2 enabled CPU: +// $ -- 8 bytes buffers +// $ crc32c 8B in 12us i.e. 41,666,666/s, aver. 0us, 317.8 MB/s +// $ xxhash32 8B in 10us i.e. 50,000,000/s, aver. 0us, 381.4 MB/s +// $ hash32 8B in 9us i.e. 55,555,555/s, aver. 0us, 423.8 MB/s +// $ -- 50 bytes buffers +// $ crc32c 50B in 11us i.e. 45,454,545/s, aver. 0us, 2.1 GB/s +// $ xxhash32 50B in 14us i.e. 35,714,285/s, aver. 0us, 1.6 GB/s +// $ hash32 50B in 10us i.e. 50,000,000/s, aver. 0us, 2.3 GB/s +// $ -- 100 bytes buffers +// $ crc32c 100B in 12us i.e. 41,666,666/s, aver. 0us, 3.8 GB/s +// $ xxhash32 100B in 19us i.e. 26,315,789/s, aver. 0us, 2.4 GB/s +// $ hash32 100B in 13us i.e. 38,461,538/s, aver. 0us, 3.5 GB/s +// $ -- 1000 bytes buffers +// $ crc32c 0.9KB in 37us i.e. 13,513,513/s, aver. 0us, 12.5 GB/s +// $ xxhash32 0.9KB in 96us i.e. 5,208,333/s, aver. 0us, 4.8 GB/s +// $ hash32 0.9KB in 62us i.e. 8,064,516/s, aver. 0us, 7.5 GB/s +// $ -- 10000 bytes buffers +// $ crc32c 9.7KB in 282us i.e. 1,773,049/s, aver. 0us, 16.5 GB/s +// $ xxhash32 9.7KB in 927us i.e. 539,374/s, aver. 1us, 5 GB/s +// $ hash32 9.7KB in 487us i.e. 1,026,694/s, aver. 0us, 9.5 GB/s +function Hash32(Data: PCardinalArray; Len: integer): cardinal; overload; + +// our custom hash/checsum function, specialized for Text comparaison +// - it is a checksum algorithm, not a hash function: has less colision than +// Adler32 for short strings, but more than xxhash32 or crc32/crc32c +// - is faster than CRC32 or Adler32, since uses DQWord (128-bit) aligned read +// - overloaded function using RawByteString for binary content hashing, +// whatever the codepage is function Hash32(const Text: RawByteString): cardinal; overload; {$ifdef HASINLINE}inline;{$endif} @@ -11404,6 +11419,12 @@ TQWordRec = record THash128 = array[0..15] of byte; /// pointer to a 128-bit hash value PHash128 = ^THash128; + /// store a 160-bit hash value + // - e.g. a SHA-1 digest + // - consumes 20 bytes of memory + THash160 = array[0..19] of byte; + /// pointer to a 160-bit hash value + PHash160 = ^THash160; /// store a 192-bit hash value // - consumes 24 bytes of memory THash192 = array[0..23] of byte; @@ -11503,8 +11524,10 @@ TQWordRec = record 3: (i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14,i15: integer); 4: (c0,c1,c2,c3: TBlock128); 5: (b: THash512); - 6: (b3: THash384); - 7: (w: array[0..31] of word); + 6: (b160: THash160); + 7: (b384: THash384); + 8: (w: array[0..31] of word); + 9: (c: array[0..15] of cardinal); end; /// pointer to 512-bit hash map variable record PHash512Rec = ^THash512Rec; @@ -11568,6 +11591,23 @@ procedure IP6Text(ip6: PHash128; result: PShortString); overload; // - by design, such combined hashes cannot be cascaded procedure crc256c(buf: PAnsiChar; len: cardinal; out crc: THash256); +/// returns TRUE if all 20 bytes of this 160-bit buffer equal zero +// - e.g. a SHA-1 digest +function IsZero(const dig: THash160): boolean; overload; + {$ifdef HASINLINE}inline;{$endif} + +/// returns TRUE if all 20 bytes of both 160-bit buffers do match +// - e.g. a SHA-1 digest +// - this function is not sensitive to any timing attack, so is designed +// for cryptographic purpose +function IsEqual(const A,B: THash160): boolean; overload; + {$ifdef HASINLINE}inline;{$endif} + +/// fill all 20 bytes of this 160-bit buffer with zero +// - may be used to cleanup stack-allocated content +// ! ... finally FillZero(digest); end; +procedure FillZero(out dig: THash160); overload; + /// returns TRUE if all 32 bytes of this 256-bit buffer equal zero // - e.g. a SHA-256 digest, or a TECCSignature result function IsZero(const dig: THash256): boolean; overload; @@ -11650,6 +11690,7 @@ procedure FillZero(var dest; count: PtrInt); overload; /// fast computation of two 64-bit unsigned integers into a 128-bit value procedure mul64x64(const left, right: QWord; out product: THash128Rec); + {$ifdef FPC}{$ifndef CPUX64}inline;{$endif CPUX64}{$endif FPC} type /// the potential features, retrieved from an Intel CPU @@ -11670,11 +11711,11 @@ procedure mul64x64(const left, right: QWord; out product: THash128Rec); cfBMI2, cfERMS, cfINVPCID, cfRTM, cfPQM, cf_b13, cfMPX, cfPQE, cfAVX512F, cfAVX512DQ, cfRDSEED, cfADX, cfSMAP, cfAVX512IFMA, cfPCOMMIT, cfCLFLUSH, cfCLWB, cfIPT, cfAVX512PF, cfAVX512ER, cfAVX512CD, cfSHA, cfAVX512BW, cfAVX512VL, - cfPREFW1, cfAVX512VBMI, cfUMIP, cfPKU, cfOSPKE, cf_c05, cf_c06, cf_c07, - cf_c08, cf_c09, cf_c10, cf_c11, cf_c12, cf_c13, cfAVX512VPC, cf_c15, + cfPREFW1, cfAVX512VBMI, cfUMIP, cfPKU, cfOSPKE, cf_c05, cfAVX512VBMI2, cf_c07, + cfGFNI, cfVAES, cfVCLMUL, cfAVX512NNI, cfAVX512BITALG, cf_c13, cfAVX512VPC, cf_c15, cf_cc16, cf_c17, cf_c18, cf_c19, cf_c20, cf_c21, cfRDPID, cf_c23, cf_c24, cf_c25, cf_c26, cf_c27, cf_c28, cf_c29, cfSGXLC, cf_c31, - cf_d0, cf_d1, cfAVX512NNI, cfAVX512MAS, cf_d4, cf_d5, cf_d6, cf_d7); + cf_d0, cf_d1, cfAVX512NNIW, cfAVX512MAS, cf_d4, cf_d5, cf_d6, cf_d7); /// all features, as retrieved from an Intel CPU TIntelCpuFeatures = set of TIntelCpuFeature; @@ -11697,7 +11738,7 @@ function ToText(const aIntelCPUFeatures: TIntelCpuFeatures; function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; {$endif CPUINTEL} -/// naive symmetric encryption scheme using a 32 bit key +/// naive symmetric encryption scheme using a 32-bit key // - fast, but not very secure, since uses crc32ctab[] content as master cypher // key: consider using SynCrypto proven AES-based algorithms instead procedure SymmetricEncrypt(key: cardinal; var data: RawByteString); @@ -11707,7 +11748,9 @@ procedure SymmetricEncrypt(key: cardinal; var data: RawByteString); var /// compute CRC32C checksum on the supplied buffer - // - this variable will use the fastest mean available, e.g. SSE 4.2 + // - result is not compatible with zlib's crc32() - Intel/SCSI CRC32C is not + // the same polynom - but will use the fastest mean available, e.g. SSE 4.2, + // to achieve up to 16GB/s with the optimized implementation from SynCrypto.pas // - you should use this function instead of crc32cfast() or crc32csse42() crc32c: THasher; /// compute CRC32C checksum on one 32-bit unsigned integer @@ -11734,16 +11777,31 @@ function crc32cUTF8ToHex(const str: RawUTF8): RawUTF8; InterningHasher: THasher; /// retrieve a particular bit status from a bit array +// - this function can't be inlined, whereas GetBitPtr() function can function GetBit(const Bits; aIndex: PtrInt): boolean; - {$ifndef CPUINTEL}inline;{$endif} /// set a particular bit into a bit array +// - this function can't be inlined, whereas SetBitPtr() function can procedure SetBit(var Bits; aIndex: PtrInt); - {$ifndef CPUINTEL}inline;{$endif} /// unset/clear a particular bit into a bit array +// - this function can't be inlined, whereas UnSetBitPtr() function can procedure UnSetBit(var Bits; aIndex: PtrInt); - {$ifndef CPUINTEL}inline;{$endif} + +/// retrieve a particular bit status from a bit array +// - GetBit() can't be inlined, whereas this pointer-oriented function can +function GetBitPtr(Bits: pointer; aIndex: PtrInt): boolean; + {$ifdef HASINLINE}inline;{$endif} + +/// set a particular bit into a bit array +// - SetBit() can't be inlined, whereas this pointer-oriented function can +procedure SetBitPtr(Bits: pointer; aIndex: PtrInt); + {$ifdef HASINLINE}inline;{$endif} + +/// unset/clear a particular bit into a bit array +// - UnSetBit() can't be inlined, whereas this pointer-oriented function can +procedure UnSetBitPtr(Bits: pointer; aIndex: PtrInt); + {$ifdef HASINLINE}inline;{$endif} /// compute the number of bits set in a bit array // - Count is the bit count, not byte size @@ -11759,8 +11817,8 @@ function GetBitsCount(const Bits; Count: PtrInt): integer; 1 shl 25-1, 1 shl 26-1, 1 shl 27-1, 1 shl 28-1, 1 shl 29-1, 1 shl 30-1, $7fffffff, $ffffffff); -/// returns TRUE if all BitCount bits are set in the input cardinal -function GetAllBits(Bits: Cardinal; BitCount: Integer): boolean; +/// returns TRUE if all BitCount bits are set in the input 32-bit cardinal +function GetAllBits(Bits, BitCount: cardinal): boolean; {$ifdef HASINLINE}inline;{$endif} type @@ -11773,21 +11831,21 @@ function GetAllBits(Bits: Cardinal; BitCount: Integer): boolean; // - the compiler will generate bt/btr/bts opcodes TBits32 = set of 0..31; PBits32 = ^TBits32; - /// fast access to Int64 bits + /// fast access to 64-bit integer bits // - the compiler will generate bt/btr/bts opcodes // - as used by GetBit64/SetBit64/UnSetBit64 TBits64 = set of 0..63; PBits64 = ^TBits64; -/// retrieve a particular bit status from a Int64 bit array (max aIndex is 63) +/// retrieve a particular bit status from a 64-bit integer bits (max aIndex is 63) function GetBit64(const Bits: Int64; aIndex: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} -/// set a particular bit into a Int64 bit array (max aIndex is 63) +/// set a particular bit into a 64-bit integer bits (max aIndex is 63) procedure SetBit64(var Bits: Int64; aIndex: PtrInt); {$ifdef HASINLINE}inline;{$endif} -/// unset/clear a particular bit into a Int64 bit array (max aIndex is 63) +/// unset/clear a particular bit into a 64-bit integer bits (max aIndex is 63) procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt); {$ifdef HASINLINE}inline;{$endif} @@ -11855,283 +11913,6 @@ procedure SetThreadNameDefault(ThreadID: TThreadID; const Name: RawUTF8); SetThreadNameInternal: procedure(ThreadID: TThreadID; const Name: RawUTF8) = SetThreadNameDefault; -{$ifndef LVCL} // LVCL does not implement TEvent - -type - {$M+} - TSynBackgroundThreadAbstract = class; - TSynBackgroundThreadEvent = class; - {$M-} - - /// idle method called by TSynBackgroundThreadAbstract in the caller thread - // during remote blocking process in a background thread - // - typical use is to run Application.ProcessMessages, e.g. for - // TSQLRestClientURI.URI() to provide a responsive UI even in case of slow - // blocking remote access - // - provide the time elapsed (in milliseconds) from the request start (can be - // used e.g. to popup a temporary message to wait) - // - is call once with ElapsedMS=0 at request start - // - is call once with ElapsedMS=-1 at request ending - // - see TLoginForm.OnIdleProcess and OnIdleProcessForm in mORMotUILogin.pas - TOnIdleSynBackgroundThread = procedure(Sender: TSynBackgroundThreadAbstract; - ElapsedMS: Integer) of object; - - /// event prototype used e.g. by TSynBackgroundThreadAbstract callbacks - // - a similar signature is defined in SynCrtSock and LVCL.Classes - TNotifyThreadEvent = procedure(Sender: TThread) of object; - - /// abstract TThread with its own execution content - // - you should not use this class directly, but use either - // TSynBackgroundThreadMethodAbstract / TSynBackgroundThreadEvent / - // TSynBackgroundThreadMethod and provide a much more convenient callback - TSynBackgroundThreadAbstract = class(TThread) - protected - fProcessEvent: TEvent; - fOnBeforeExecute: TNotifyThreadEvent; - fOnAfterExecute: TNotifyThreadEvent; - fThreadName: RawUTF8; - fExecute: (exCreated,exRun,exFinished); - fExecuteLoopPause: boolean; - procedure SetExecuteLoopPause(dopause: boolean); - /// where the main process takes place - procedure Execute; override; - procedure ExecuteLoop; virtual; abstract; - public - /// initialize the thread - // - you could define some callbacks to nest the thread execution, e.g. - // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread - constructor Create(const aThreadName: RawUTF8; OnBeforeExecute: TNotifyThreadEvent=nil; - OnAfterExecute: TNotifyThreadEvent=nil; CreateSuspended: boolean=false); reintroduce; - /// release used resources - destructor Destroy; override; - {$ifndef HASTTHREADSTART} - /// method to be called to start the thread - // - Resume is deprecated in the newest RTL, since some OS - e.g. Linux - - // do not implement this pause/resume feature; we define here this method - // for older versions of Delphi - procedure Start; - {$endif} - {$ifdef HASTTHREADTERMINATESET} - /// properly terminate the thread - // - called by TThread.Terminate - procedure TerminatedSet; override; - {$else} - /// properly terminate the thread - // - called by reintroduced Terminate - procedure TerminatedSet; virtual; - /// reintroduced to call TeminatedSet - procedure Terminate; reintroduce; - {$endif} - /// wait for Execute/ExecuteLoop to be ended (i.e. fExecute<>exRun) - procedure WaitForNotExecuting(maxMS: integer=500); - /// temporary stop the execution of ExecuteLoop, until set back to false - // - may be used e.g. by TSynBackgroundTimer to delay the process of - // background tasks - property Pause: boolean read fExecuteLoopPause write SetExecuteLoopPause; - /// access to the low-level associated event used to notify task execution - // to the background thread - // - you may call ProcessEvent.SetEvent to trigger the internal process loop - property ProcessEvent: TEvent read fProcessEvent; - /// defined as public since may be used to terminate the processing methods - property Terminated; - end; - - /// state machine status of the TSynBackgroundThreadAbstract process - TSynBackgroundThreadProcessStep = ( - flagIdle, flagStarted, flagFinished, flagDestroying); - - /// state machine statuses of the TSynBackgroundThreadAbstract process - TSynBackgroundThreadProcessSteps = set of TSynBackgroundThreadProcessStep; - - /// abstract TThread able to run a method in its own execution content - // - typical use is a background thread for processing data or remote access, - // while the UI will be still responsive by running OnIdle event in loop: see - // e.g. how TSQLRestClientURI.OnIdle handle this in mORMot.pas unit - // - you should not use this class directly, but inherit from it and override - // the Process method, or use either TSynBackgroundThreadEvent / - // TSynBackgroundThreadMethod and provide a much more convenient callback - TSynBackgroundThreadMethodAbstract = class(TSynBackgroundThreadAbstract) - protected - fCallerEvent: TEvent; - fParam: pointer; - fCallerThreadID: TThreadID; - fBackgroundException: Exception; - fOnIdle: TOnIdleSynBackgroundThread; - fOnBeforeProcess: TNotifyThreadEvent; - fOnAfterProcess: TNotifyThreadEvent; - fPendingProcessFlag: TSynBackgroundThreadProcessStep; - fPendingProcessLock: TSynLocker; - procedure ExecuteLoop; override; - function OnIdleProcessNotify(start: Int64): integer; - function GetOnIdleBackgroundThreadActive: boolean; - function GetPendingProcess: TSynBackgroundThreadProcessStep; - procedure SetPendingProcess(State: TSynBackgroundThreadProcessStep); - // returns flagIdle if acquired, flagDestroying if terminated - function AcquireThread: TSynBackgroundThreadProcessStep; - procedure WaitForFinished(start: Int64); - /// called by Execute method when fProcessParams<>nil and fEvent is notified - procedure Process; virtual; abstract; - public - /// initialize the thread - // - if aOnIdle is not set (i.e. equals nil), it will simply wait for - // the background process to finish until RunAndWait() will return - // - you could define some callbacks to nest the thread execution, e.g. - // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread - constructor Create(aOnIdle: TOnIdleSynBackgroundThread; - const aThreadName: RawUTF8; OnBeforeExecute: TNotifyThreadEvent=nil; - OnAfterExecute: TNotifyThreadEvent=nil); reintroduce; - /// finalize the thread - destructor Destroy; override; - /// launch Process abstract method asynchronously in the background thread - // - wait until process is finished, calling OnIdle() callback in - // the meanwhile - // - any exception raised in background thread will be translated in the - // caller thread - // - returns false if self is not set, or if called from the same thread - // as it is currently processing (to avoid race condition from OnIdle() - // callback) - // - returns true when the background process is finished - // - OpaqueParam will be used to specify a thread-safe content for the - // background process - // - this method is thread-safe, that is it will wait for any started process - // already launch by another thread: you may call this method from any - // thread, even if its main purpose is to be called from the main UI thread - function RunAndWait(OpaqueParam: pointer): boolean; - /// set a callback event to be executed in loop during remote blocking - // process, e.g. to refresh the UI during a somewhat long request - // - you can assign a callback to this property, calling for instance - // Application.ProcessMessages, to execute the remote request in a - // background thread, but let the UI still be reactive: the - // TLoginForm.OnIdleProcess and OnIdleProcessForm methods of - // mORMotUILogin.pas will match this property expectations - // - if OnIdle is not set (i.e. equals nil), it will simply wait for - // the background process to finish until RunAndWait() will return - property OnIdle: TOnIdleSynBackgroundThread read fOnIdle write fOnIdle; - /// TRUE if the background thread is active, and OnIdle event is called - // during process - // - to be used e.g. to ensure no re-entrance from User Interface messages - property OnIdleBackgroundThreadActive: Boolean read GetOnIdleBackgroundThreadActive; - /// optional callback event triggered in Execute before each Process - property OnBeforeProcess: TNotifyThreadEvent read fOnBeforeProcess write fOnBeforeProcess; - /// optional callback event triggered in Execute after each Process - property OnAfterProcess: TNotifyThreadEvent read fOnAfterProcess write fOnAfterProcess; - end; - - /// background process method called by TSynBackgroundThreadEvent - // - will supply the OpaqueParam parameter as provided to RunAndWait() - // method when the Process virtual method will be executed - TOnProcessSynBackgroundThread = procedure(Sender: TSynBackgroundThreadEvent; - ProcessOpaqueParam: pointer) of object; - - /// allow background thread process of a method callback - TSynBackgroundThreadEvent = class(TSynBackgroundThreadMethodAbstract) - protected - fOnProcess: TOnProcessSynBackgroundThread; - /// just call the OnProcess handler - procedure Process; override; - public - /// initialize the thread - // - if aOnIdle is not set (i.e. equals nil), it will simply wait for - // the background process to finish until RunAndWait() will return - constructor Create(aOnProcess: TOnProcessSynBackgroundThread; - aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); reintroduce; - /// provide a method handler to be execute in the background thread - // - triggered by RunAndWait() method - which will wait until finished - // - the OpaqueParam as specified to RunAndWait() will be supplied here - property OnProcess: TOnProcessSynBackgroundThread read fOnProcess write fOnProcess; - end; - - /// allow background thread process of a variable TThreadMethod callback - TSynBackgroundThreadMethod = class(TSynBackgroundThreadMethodAbstract) - protected - /// just call the TThreadMethod, as supplied to RunAndWait() - procedure Process; override; - public - /// run once the supplied TThreadMethod callback - // - use this method, and not the inherited RunAndWait() - procedure RunAndWait(Method: TThreadMethod); reintroduce; - end; - - /// background process procedure called by TSynBackgroundThreadProcedure - // - will supply the OpaqueParam parameter as provided to RunAndWait() - // method when the Process virtual method will be executed - TOnProcessSynBackgroundThreadProc = procedure(ProcessOpaqueParam: pointer); - - /// allow background thread process of a procedure callback - TSynBackgroundThreadProcedure = class(TSynBackgroundThreadMethodAbstract) - protected - fOnProcess: TOnProcessSynBackgroundThreadProc; - /// just call the OnProcess handler - procedure Process; override; - public - /// initialize the thread - // - if aOnIdle is not set (i.e. equals nil), it will simply wait for - // the background process to finish until RunAndWait() will return - constructor Create(aOnProcess: TOnProcessSynBackgroundThreadProc; - aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); reintroduce; - /// provide a procedure handler to be execute in the background thread - // - triggered by RunAndWait() method - which will wait until finished - // - the OpaqueParam as specified to RunAndWait() will be supplied here - property OnProcess: TOnProcessSynBackgroundThreadProc read fOnProcess write fOnProcess; - end; - - /// an exception which would be raised by TSynParallelProcess - ESynParallelProcess = class(ESynException); - - /// callback implementing some parallelized process for TSynParallelProcess - // - if 0<=IndexStart<=IndexStop, it should execute some process - TSynParallelProcessMethod = procedure(IndexStart, IndexStop: integer) of object; - - /// thread executing process for TSynParallelProcess - TSynParallelProcessThread = class(TSynBackgroundThreadMethodAbstract) - protected - fMethod: TSynParallelProcessMethod; - fIndexStart, fIndexStop: integer; - procedure Start(Method: TSynParallelProcessMethod; IndexStart,IndexStop: integer); - /// executes fMethod(fIndexStart,fIndexStop) - procedure Process; override; - public - end; - - /// allow parallel execution of an index-based process in a thread pool - // - will create its own thread pool, then execute any method by spliting the - // work into each thread - TSynParallelProcess = class(TSynPersistentLocked) - protected - fThreadName: RawUTF8; - fPool: array of TSynParallelProcessThread; - fThreadPoolCount: integer; - fParallelRunCount: integer; - public - /// initialize the thread pool - // - you could define some callbacks to nest the thread execution, e.g. - // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread - // - up to MaxThreadPoolCount=32 threads could be setup (you may allow a - // bigger value, but interrest of this thread pool is to have its process - // saturating each CPU core) - // - if ThreadPoolCount is 0, no thread would be created, and process - // would take place in the current thread - constructor Create(ThreadPoolCount: integer; const ThreadName: RawUTF8; - OnBeforeExecute: TNotifyThreadEvent=nil; OnAfterExecute: TNotifyThreadEvent=nil; - MaxThreadPoolCount: integer = 32); reintroduce; virtual; - /// finalize the thread pool - destructor Destroy; override; - /// run a method in parallel, and wait for the execution to finish - // - will split Method[0..MethodCount-1] execution over the threads - // - in case of any exception during process, an ESynParallelProcess - // exception would be raised by this method - procedure ParallelRunAndWait(Method: TSynParallelProcessMethod; MethodCount: integer); - published - /// how many threads have been activated - property ParallelRunCount: integer read fParallelRunCount; - /// how many threads are currently in this instance thread pool - property ThreadPoolCount: integer read fThreadPoolCount; - /// some text identifier, used to distinguish each owned thread - property ThreadName: RawUTF8 read fThreadName; - end; - -{$endif LVCL} // LVCL does not implement TEvent - /// low-level wrapper to add a callback to a dynamic list of events // - by default, you can assign only one callback to an Event: but by storing @@ -12211,8 +11992,9 @@ function EventEquals(const eventA,eventB): boolean; /// a cross-platform and cross-compiler TSystemTime structure // - FPC's TSystemTime in datih.inc does NOT match Windows TSystemTime fields! - // - also used to store a Date/Time in TSynTimeZone internal structures - {$ifdef UNICODE}TSynSystemTime = record{$else}TSynSystemTime = object{$endif} + // - also used to store a Date/Time in TSynTimeZone internal structures, or + // for fast conversion from TDateTime to its ready-to-display members + {$ifdef FPC_OR_UNICODE}TSynSystemTime = record{$else}TSynSystemTime = object{$endif} public Year, Month, DayOfWeek, Day, Hour, Minute, Second, MilliSecond: word; @@ -12228,12 +12010,32 @@ {$ifdef UNICODE}TSynSystemTime = record{$else}TSynSystemTime = object{$endif} procedure FromNowUTC; /// fill fields with the current Local time, using a 8-16ms thread-safe cache procedure FromNowLocal; + /// fill fields from the given value - but not DayOfWeek + procedure FromDateTime(const dt: TDateTime); + /// fill Year/Month/Day fields from the given value - but not DayOfWeek + // - faster than the RTL DecodeDate() function + procedure FromDate(const dt: TDateTime); + /// fill Hour/Minute/Second/Millisecond fields from the given value + // - faster than the RTL DecodeTime() function + procedure FromTime(const dt: TDateTime); /// encode the stored date/time as text function ToText(Expanded: boolean=true; FirstTimeChar: AnsiChar='T'; const TZD: RawUTF8=''): RawUTF8; /// append the stored date and time, in a log-friendly format - // - e.g. append '20110325 19241502 ' + // - e.g. append '20110325 19241502' - with no trailing space nor tab // - as called by TTextWriter.AddCurrentLogTime() procedure AddLogTime(WR: TTextWriter); + /// append the stored data and time, in apache-like format, to a TTextWriter + // - e.g. append '19/Feb/2019:06:18:55 ' - including a trailing space + procedure AddNCSAText(WR: TTextWriter); + /// append the stored data and time, in apache-like format, to a memory buffer + // - e.g. append '19/Feb/2019:06:18:55 ' - including a trailing space + // - returns the number of chars added to P, i.e. always 21 + function ToNCSAText(P: PUTF8Char): PtrInt; + /// convert the stored time into a TDateTime + function ToDateTime: TDateTime; + /// add some 1..999 milliseconds to the stored time + // - not to be used for computation, but e.g. for fast AddLogTime generation + procedure IncrementMS(ms: integer); end; PSynSystemTime = ^TSynSystemTime; @@ -12266,7 +12068,7 @@ {$ifdef UNICODE}TSynSystemTime = record{$else}TSynSystemTime = object{$endif} // temporary conversion in such case // - TTimeLogBits.Value has a 38-bit precision, so features exact representation // as JavaScript numbers (stored in a 52-bit mantissa) - {$ifdef UNICODE}TTimeLogBits = record{$else}TTimeLogBits = object{$endif} + {$ifdef FPC_OR_UNICODE}TTimeLogBits = record{$else}TTimeLogBits = object{$endif} public /// the bit-encoded value itself, which follows an abstract "year" of 16 // months of 32 days of 32 hours of 64 minutes of 64 seconds @@ -12498,7 +12300,7 @@ function TimeToIso8601(Time: TDateTime; Expanded: boolean; FirstChar: AnsiChar=' /// Write a Date to P^ Ansi buffer // - if Expanded is false, 'YYYYMMDD' date format is used // - if Expanded is true, 'YYYY-MM-DD' date format is used -procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: cardinal); overload; +procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: PtrUInt); overload; /// convert a date into 'YYYY-MM-DD' date format // - resulting text is compatible with all ISO-8601 functions @@ -12552,7 +12354,7 @@ procedure DateTimeToIso8601StringVar(DT: TDateTime; FirstChar: AnsiChar; var res // - if Expanded is true, 'Thh:mm:ss' time format is used // - you can custom the first char in from of the resulting text time // - if WithMS is TRUE, will append MS as '.sss' for milliseconds resolution -procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S,MS: cardinal; +procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S,MS: PtrUInt; FirstChar: AnsiChar = 'T'; WithMS: boolean=false); overload; /// Write a Time to P^ Ansi buffer @@ -12707,6 +12509,7 @@ function UnixMSTimeToDateTime(const UnixMSTime: TUnixMSTime): TDateTime; {$ifdef HASINLINE}inline;{$endif} /// convert a TDateTime into a millisecond-based c-encoded time (from Unix epoch 1/1/1970) +// - if AValue is 0, will return 0 (since is likely to be an error constant) function DateTimeToUnixMSTime(const AValue: TDateTime): TUnixMSTime; {$ifdef HASINLINE}inline;{$endif} @@ -12753,7 +12556,7 @@ TTimeZoneInfo = record TTimeZoneID = type RawUTF8; /// used to store Time Zone information for a single area in TSynTimeZone - {$ifdef UNICODE}TTimeZoneData = record{$else}TTimeZoneData = object{$endif} + {$ifdef FPC_OR_UNICODE}TTimeZoneData = record{$else}TTimeZoneData = object{$endif} public id: TTimeZoneID; display: RawUTF8; @@ -12902,7 +12705,8 @@ procedure LogToTextFile(Msg: RawUTF8); // - this version expects the filename to be specified // - format contains the current date and time, then the Msg on one line // - date and time format used is 'YYYYMMDD hh:mm:ss' -procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName; aMaxSize: Int64=MAXLOGSIZE); +procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName; aMaxSize: Int64=MAXLOGSIZE; + aUTCTimeStamp: boolean=false); { ************ fast low-level lookup types used by internal conversion routines } @@ -13047,7 +12851,7 @@ TFileVersion = class // ExeVersion global variable constructor Create(const aFileName: TFileName; aMajor: integer=0; aMinor: integer=0; aRelease: integer=0; aBuild: integer=0); - /// retrieve the version as a 32 bits integer with Major.Minor.Release + /// retrieve the version as a 32-bit integer with Major.Minor.Release // - following Major shl 16+Minor shl 8+Release bit pattern function Version32: integer; /// build date and time of this exe file, as plain text @@ -13059,6 +12863,11 @@ TFileVersion = class // - includes FileName (without path), Detailed and BuildDateTime properties // - e.g. 'myprogram.exe 3.1.0.123 2016-06-14 19:07:55' function VersionInfo: RawUTF8; + /// returns a ready-to-use User-Agent header with exe name, version and OS + // - e.g. 'myprogram/3.1.0.123W32' + // - here OS_INITIAL[] character is used to identify the OS, with '32' + // appended on 32-bit Windows + function UserAgent: RawUTF8; /// returns the version information of a specified exe file as text // - includes FileName (without path), Detailed and BuildDateTime properties // - e.g. 'myprogram.exe 3.1.0.123 2016-06-14 19:07:55' @@ -13090,7 +12899,7 @@ TFileVersion = class DateDelta = 693594; UnixDateDelta = 25569; -/// GetFileVersion returns the most significant 32 bits of a file's binary +/// GetFileVersion returns the most significant 32-bit of a file's binary // version number // - typically, this includes the major and minor version placed // together in one 32-bit integer @@ -13098,15 +12907,11 @@ TFileVersion = class // - returns Cardinal(-1) in case of failure function GetFileVersion(const FileName: TFileName): cardinal; -{$endif} - -/// returns a JSON object containing basic information about the computer -// - including Host, User, CPU, OS, freemem, freedisk... -function SystemInfoJson: RawUTF8; +{$endif DELPHI6OROLDER} type /// the recognized operating systems - // - it will also recognize some of the distributions + // - it will also recognize some Linux distributions TOperatingSystem = (osUnknown, osWindows, osLinux, osOSX, osBSD, osPOSIX, osArch, osAurox, osDebian, osFedora, osGentoo, osKnoppix, osMint, osMandrake, osMandriva, osNovell, osUbuntu, osSlackware, osSolaris, osSuse, osSynology, @@ -13120,7 +12925,7 @@ function SystemInfoJson: RawUTF8; wSeven, wSeven_64, wServer2008_R2, wServer2008_R2_64, wEight, wEight_64, wServer2012, wServer2012_64, wEightOne, wEightOne_64, wServer2012R2, wServer2012R2_64, - wTen, wTen_64, wServer2016, wServer2016_64); + wTen, wTen_64, wServer2016, wServer2016_64, wServer2019_64); /// the running Operating System, encoded as a 32-bit integer TOperatingSystemVersion = packed record case os: TOperatingSystem of @@ -13138,7 +12943,19 @@ function SystemInfoJson: RawUTF8; '7', '7 64bit', 'Server 2008 R2', 'Server 2008 R2 64bit', '8', '8 64bit', 'Server 2012', 'Server 2012 64bit', '8.1', '8.1 64bit', 'Server 2012 R2', 'Server 2012 R2 64bit', - '10', '10 64bit', 'Server 2016', 'Server 2016 64bit'); + '10', '10 64bit', 'Server 2016', 'Server 2016 64bit', 'Server 2019 64bit'); + /// the recognized Windows versions which are 32-bit + WINDOWS_32 = [w2000, wXP, wServer2003, wServer2003_R2, wVista, wServer2008, + wSeven, wServer2008_R2, wEight, wServer2012, wEightOne, wServer2012R2, + wTen, wServer2016]; + /// translate one operating system (and distribution) into a single character + // - may be used internally e.g. for a HTTP User-Agent header + OS_INITIAL: array[TOperatingSystem] of AnsiChar = + ('?', 'W', 'L', 'X', 'B', 'P', 'A', 'a', 'D', 'F', 'G', 'K', 'M', 'm', + 'n', 'N', 'U', 'S', 's', 'u', 'Y', 'T', 'C', 't', 'R', 'l', 'O', 'G', + 'c', 'd', 'x', 'Z', 'r', 'p'); + /// the operating systems items which actually are Linux distributions + OS_LINUX = [osLinux, osArch .. osAlpine]; /// the compiler family used COMP_TEXT = {$ifdef FPC}'Fpc'{$else}'Delphi'{$endif}; @@ -13154,6 +12971,8 @@ function SystemInfoJson: RawUTF8; {$ifdef CPU32}'32'{$else}'64'{$endif}{$endif}{$endif}; function ToText(os: TOperatingSystem): PShortString; overload; +function ToText(const osv: TOperatingSystemVersion): ShortString; overload; +function ToTextOS(osint32: integer): RawUTF8; var /// the target Operating System used for compilation, as TOperatingSystem @@ -13168,7 +12987,7 @@ function ToText(os: TOperatingSystem): PShortString; overload; CpuInfoText: RawUTF8; /// some textual information about the current computer hardware, from BIOS BiosInfoText: RawUTF8; - /// the running Operating System information, encoded as a 32-bit integer + /// the running Operating System OSVersion32: TOperatingSystemVersion; OSVersionInt32: integer absolute OSVersion32; @@ -13192,10 +13011,10 @@ TOSVersionInfoEx = record {$endif UNICODE} var - /// is set to TRUE if the current process is a 32 bit image running under WOW64 + /// is set to TRUE if the current process is a 32-bit image running under WOW64 // - WOW64 is the x86 emulator that allows 32-bit Windows-based applications // to run seamlessly on 64-bit Windows - // - equals always FALSE if the current executable is a 64 bit image + // - equals always FALSE if the current executable is a 64-bit image IsWow64: boolean; /// the current System information, as retrieved for the current process // - under a WOW64 process, it will use the GetNativeSystemInfo() new API @@ -13209,12 +13028,6 @@ TOSVersionInfoEx = record /// the current Operating System version, as retrieved for the current process OSVersion: TWindowsVersion; -/// a wrapper around EnumProcesses() PsAPI call -function EnumAllProcesses(out Count: Cardinal): TCardinalDynArray; - -/// a wrapper around QueryFullProcessImageNameW/GetModuleFileNameEx PsAPI call -function EnumProcessName(PID: Cardinal): RawUTF8; - /// this function can be used to create a GDI compatible window, able to // receive Windows Messages for fast local communication // - will return 0 on failure (window name already existing e.g.), or @@ -13246,7 +13059,9 @@ function SetAppUserModelID(const AppUserModelID: string): boolean; /// the number of milliseconds that have elapsed since the system was started // - compatibility function, to be implemented according to the running OS // - will use the corresponding native API function under Vista+, or - // will emulate it for older Windows versions + // will emulate it for older Windows versions (XP) + // - warning: FPC's SysUtils.GetTickCount64 or TThread.GetTickCount64 don't + // handle properly 49 days wrapping under XP -> always use this safe version GetTickCount64: function: Int64; stdcall; /// similar to Windows sleep() API call, to be truly cross-platform @@ -13286,15 +13101,17 @@ function FileTimeToUnixMSTime(const FT: TFileTime): TUnixMSTime; function GetCurrentThreadID: TThreadID; cdecl; external 'libpthread.so.0' name 'pthread_self'; -/// overloaded function using open64() to allow 64 bit positions +/// overloaded function using open64() to allow 64-bit positions function FileOpen(const FileName: string; Mode: LongWord): Integer; {$endif} /// compatibility function, to be implemented according to the running OS -// - expect more or less the same result as the homonymous Win32 API function -// - will call the corresponding function in SynKylix.pas or SynFPCLinux.pas -function GetTickCount64: Int64; {$ifdef HASINLINE}inline;{$endif} +// - expect more or less the same result as the homonymous Win32 API function, +// but usually with a better resolution (Windows has only around 10-16 ms) +// - will call the corresponding function in SynKylix.pas or SynFPCLinux.pas, +// using the very fast CLOCK_MONOTONIC_COARSE if available on the kernel +function GetTickCount64: Int64; {$endif MSWINDOWS} @@ -13415,7 +13232,8 @@ procedure SetExecutableVersion(const aVersionText: RawUTF8); overload; // - will return the full path of a given kind of private or shared folder, // depending on the underlying operating system // - will use SHGetFolderPath and the corresponding CSIDL constant under Windows -// - will return the $HOME folder (whatever kind value is given) otherwise +// - under POSIX, will return $TMP/$TMPDIR folder for spTempFolder, ~/.cache/appname +// for spUserData, /var/log for spLog, or the $HOME folder // - returned folder name contains the trailing path delimiter (\ or /) function GetSystemPath(kind: TSystemPath): TFileName; @@ -13445,15 +13263,6 @@ procedure RedirectCode(Func, RedirectFunc: Pointer; Backup: PPatchCode=nil); procedure RedirectCodeRestore(Func: pointer; const Backup: TPatchCode); {$endif CPUINTEL} -/// allow to fix TEvent.WaitFor() method for Kylix -// - under Windows or with FPC, will call original TEvent.WaitFor() method -function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult; - -/// allow to fix TEvent.WaitFor(Event,INFINITE) method for Kylix -// - under Windows or with FPC, will call original TEvent.WaitFor() method -procedure FixedWaitForever(Event: TEvent); - - type /// to be used instead of TMemoryStream, for speed // - allocates memory from Delphi heap (i.e. FastMM4/SynScaleMM) @@ -13566,7 +13375,9 @@ function GetNextFieldProp(var P: PUTF8Char; var Prop: RawUTF8): boolean; { ************ variant-based process, including JSON/BSON document content } const - /// this variant type is not defined in older versions of Delphi + /// unsigned 64bit integer variant type + // - currently called varUInt64 in Delphi (not defined in older versions), + // and varQWord in FPC varWord64 = 21; /// this variant type will map the current SynUnicode type @@ -13583,6 +13394,7 @@ function GetNextFieldProp(var P: PUTF8Char; var Prop: RawUTF8): boolean; // ! VarClear(aVariant); // - equals private constant varDeepData in Delphi's Variants.pas and // varComplexType in FPC's variants.pp - seldom used on FPC + // - make some false positive to varBoolean and varError VTYPE_STATIC = $BFE8; /// same as Dest := TVarData(Source) for simple values @@ -14389,8 +14201,8 @@ TDocVariant = class(TSynInvokeableVariantType) // be a good idea to use DocVariantData(aVariant)^ or _Safe(aVariant)^ instead // of TDocVariantData(aVariant), if you are not sure how aVariant was allocated // (may be not _Obj/_Json, but retrieved as varByRef e.g. from late binding) - {$ifdef UNICODE}TDocVariantData = record{$else}TDocVariantData = object{$endif} - private + {$ifdef FPC_OR_UNICODE}TDocVariantData = record private + {$else}TDocVariantData = object protected{$endif} VType: TVarType; VOptions: TDocVariantOptions; (* this structure uses all TVarData available space: no filler needed! @@ -15010,8 +14822,15 @@ {$ifdef UNICODE}TDocVariantData = record{$else}TDocVariantData = object{$endif // methods for much faster O(log(n)) binary search procedure SortByName(Compare: TUTF8Compare=nil); /// sort the document object values by value - // - do nothing if the document is not a dvObject - procedure SortByValue(Compare: TVariantCompare); + // - work for both dvObject and dvArray documents + // - will sort by UTF-8 text (VariantCompare) if no custom aCompare is supplied + procedure SortByValue(Compare: TVariantCompare = nil); + /// sort the document array values by a field of some stored objet values + // - do nothing if the document is not a dvArray, or if the items are no dvObject + // - will sort by UTF-8 text (VariantCompare) if no custom aValueCompare is supplied + procedure SortArrayByField(const aItemPropName: RawUTF8; + aValueCompare: TVariantCompare=nil; aValueCompareReverse: boolean=false; + aNameSortedCompare: TUTF8Compare=nil); /// reverse the order of the document object or array items procedure Reverse; /// create a TDocVariant object, from a selection of properties of this @@ -15472,7 +15291,8 @@ procedure _UniqueFast(var DocVariant: variant); // arrays were created with: to be used on a value returned as varByRef // (e.g. by _() pseudo-method) // - for huge document with a big depth of nested objects or arrays, a full -// per-value copy may be time and resource consuming, but will be also safe +// per-value copy may be time and resource consuming, but will be also safe - +// consider using _ByRef() instead if a fast copy-by-reference is enough // - will raise an EDocVariant if the supplied variant is not a TDocVariant or // a varByRef pointing to a TDocVariant function _Copy(const DocVariant: variant): variant; @@ -15486,7 +15306,8 @@ function _Copy(const DocVariant: variant): variant; // arrays were created with: to be used on a value returned as varByRef // (e.g. by _() pseudo-method) // - for huge document with a big depth of nested objects or arrays, a full -// per-value copy may be time and resource consuming, but will be also safe +// per-value copy may be time and resource consuming, but will be also safe - +// consider using _ByRef() instead if a fast copy-by-reference is enough // - will raise an EDocVariant if the supplied variant is not a TDocVariant or // a varByRef pointing to a TDocVariant function _CopyFast(const DocVariant: variant): variant; @@ -15494,17 +15315,24 @@ function _CopyFast(const DocVariant: variant): variant; /// copy a TDocVariant to another variable, changing the options on the fly // - note that the content (items or properties) is copied by reference, -// so consider using _Copy() instead +// so consider using _Copy() instead if you expect to safely modify its content // - will return null if the supplied variant is not a TDocVariant function _ByRef(const DocVariant: variant; Options: TDocVariantOptions): variant; overload; /// copy a TDocVariant to another variable, changing the options on the fly // - note that the content (items or properties) is copied by reference, -// so consider using _Copy() instead +// so consider using _Copy() instead if you expect to safely modify its content // - will return null if the supplied variant is not a TDocVariant procedure _ByRef(const DocVariant: variant; out Dest: variant; Options: TDocVariantOptions); overload; +/// convert a TDocVariantData array or a string value into a CSV +// - will call either TDocVariantData.ToCSV, or return the string +// - returns '' if the supplied value is neither a TDocVariant or a string +// - could be used e.g. to store either a JSON CSV string or a JSON array of +// strings in a settings property +function _CSV(const DocVariantOrString: variant): RawUTF8; + /// will convert any TObject into a TDocVariant document instance // - a slightly faster alternative to Dest := _JsonFast(ObjectToJSON(Value)) // - this would convert the TObject by representation, using only serializable @@ -15795,17 +15623,19 @@ TCommandLine = class(TInterfacedObjectWithCustomCreate, ICommandLine) PPPrecisionTimer = ^PPrecisionTimer; /// high resolution timer (for accurate speed statistics) - // - WARNING: this record MUST be aligned to 32 bit, otherwise iFreq=0 - - // so you can use TLocalPrecisionTimer/ILocalPrecisionTimer if you want - // to alllocate a local timer instance on the stack - {$ifdef UNICODE}TPrecisionTimer = record{$else}TPrecisionTimer = object{$endif} - private - iStart,iStop,iResume,iLast: Int64; - iFreq: Int64; + // - WARNING: under Windows, this record MUST be aligned to 32-bit, otherwise + // iFreq=0 - so you can use TLocalPrecisionTimer/ILocalPrecisionTimer if you + // want to alllocate a local timer instance on the stack + {$ifdef FPC_OR_UNICODE}TPrecisionTimer = record private + {$else}TPrecisionTimer = object protected{$endif} + fStart,fStop,fResume,fLast: Int64; + {$ifndef LINUX} // use QueryPerformanceMicroSeconds() fast API + fWinFreq: Int64; + {$endif} /// contains the time elapsed in micro seconds between Start and Stop - iTime: TSynMonitorTotalMicroSec; + fTime: TSynMonitorTotalMicroSec; /// contains the time elapsed in micro seconds between Resume and Pause - iLastTime: TSynMonitorOneMicroSec; + fLastTime: TSynMonitorOneMicroSec; fPauseCount: TSynMonitorCount; public /// initialize the timer @@ -15814,11 +15644,10 @@ {$ifdef UNICODE}TPrecisionTimer = record{$else}TPrecisionTimer = object{$endif procedure Init; /// initialize and start the high resolution timer procedure Start; - /// returns TRUE if iStart is not 0 - function Started: boolean; - {$ifdef HASINLINE}inline;{$endif} + /// returns TRUE if fStart is not 0 + function Started: boolean; {$ifdef HASINLINE}inline;{$endif} /// stop the timer, setting the Time elapsed since last Start - procedure ComputeTime; + procedure ComputeTime; {$ifdef LINUX}{$ifdef HASINLINE}inline;{$endif}{$endif} /// stop the timer, returning the time elapsed as text with time resolution // (us,ms,s) // - is just a wrapper around ComputeTime + Time @@ -15840,6 +15669,7 @@ {$ifdef UNICODE}TPrecisionTimer = record{$else}TPrecisionTimer = object{$endif // - typical use is to declare a fTimeElapsed: TPrecisionTimer protected // member, then call fTimeElapsed.ProfileCurrentMethod at the beginning of // all process expecting some timing, then log/save fTimeElapsed.Stop content + // - FPC TIP: result should be assigned to a local variable of IUnknown type function ProfileCurrentMethod: IUnknown; /// low-level method to force values settings to allow thread safe timing // - by default, this timer is not thread safe: you can use this method to @@ -15861,20 +15691,22 @@ {$ifdef UNICODE}TPrecisionTimer = record{$else}TPrecisionTimer = object{$endif function PerSec(const Count: QWord): QWord; /// compute the time elapsed by count, with appened time resolution (us,ms,s) function ByCount(Count: QWord): TShort16; + /// returns e.g. '16.9 MB in 102.20ms i.e. 165.5 MB/s' + function SizePerSec(Size: QWord): shortstring; /// textual representation of time after counter stopped // - with appened time resolution (us,ms,s) // - not to be used in normal code, but e.g. for custom performance analysis function Time: TShort16; /// time elapsed in micro seconds after counter stopped // - not to be used in normal code, but e.g. for custom performance analysis - property TimeInMicroSec: TSynMonitorTotalMicroSec read iTime write iTime; + property TimeInMicroSec: TSynMonitorTotalMicroSec read fTime write fTime; /// textual representation of last process timing after counter stopped // - with appened time resolution (us,ms,s) // - not to be used in normal code, but e.g. for custom performance analysis function LastTime: TShort16; /// timing in micro seconds of the last process // - not to be used in normal code, but e.g. for custom performance analysis - property LastTimeInMicroSec: TSynMonitorOneMicroSec read iLastTime write iLastTime; + property LastTimeInMicroSec: TSynMonitorOneMicroSec read fLastTime write fLastTime; /// how many times the Pause method was called, i.e. the number of tasks // processeed property PauseCount: TSynMonitorCount read fPauseCount; @@ -15898,7 +15730,7 @@ {$ifdef UNICODE}TPrecisionTimer = record{$else}TPrecisionTimer = object{$endif end; /// reference counted high resolution timer (for accurate speed statistics) - // - since TPrecisionTimer shall be 32 bit aligned, you can use this class + // - since TPrecisionTimer shall be 32-bit aligned, you can use this class // to initialize a local auto-freeing ILocalPrecisionTimer variable on stack // - to be used as such: // ! var Timer: ILocalPrecisionTimer; @@ -15926,11 +15758,9 @@ TLocalPrecisionTimer = class(TInterfacedObject,ILocalPrecisionTimer) function ByCount(Count: cardinal): RawUTF8; end; - {$M+} - /// able to serialize any cumulative timing as raw micro-seconds number or text // - "cumulative" time would add each process value, e.g. SOA methods execution - TSynMonitorTime = class + TSynMonitorTime = class(TSynPersistent) protected fMicroSeconds: TSynMonitorTotalMicroSec; function GetAsText: TShort16; @@ -15947,7 +15777,7 @@ TSynMonitorTime = class /// able to serialize any immediate timing as raw micro-seconds number or text // - "immediate" size won't accumulate, i.e. may be e.g. last process time - TSynMonitorOneTime = class + TSynMonitorOneTime = class(TSynPersistent) protected fMicroSeconds: TSynMonitorOneMicroSec; function GetAsText: TShort16; @@ -15962,9 +15792,17 @@ TSynMonitorOneTime = class property Text: TShort16 read GetAsText; end; + TSynMonitorSizeParent = class(TSynPersistent) + protected + fTextNoSpace: boolean; + public + /// initialize the instance + constructor Create(aTextNoSpace: boolean); reintroduce; + end; + /// able to serialize any cumulative size as bytes number // - "cumulative" time would add each process value, e.g. global IO consumption - TSynMonitorSize = class + TSynMonitorSize = class(TSynMonitorSizeParent) protected fBytes: TSynMonitorTotalBytes; function GetAsText: TShort16; @@ -15978,7 +15816,7 @@ TSynMonitorSize = class /// able to serialize any immediate size as bytes number // - "immediate" size won't accumulate, i.e. may be e.g. computer free memory // at a given time - TSynMonitorOneSize = class + TSynMonitorOneSize = class(TSynMonitorSizeParent) protected fBytes: TSynMonitorOneBytes; function GetAsText: TShort16; @@ -15992,7 +15830,7 @@ TSynMonitorOneSize = class /// able to serialize any bandwith as bytes count per second // - is usually associated with TSynMonitorOneSize properties, // e.g. to monitor IO activity - TSynMonitorThroughput = class + TSynMonitorThroughput = class(TSynMonitorSizeParent) protected fBytesPerSec: QWord; function GetAsText: TShort16; @@ -16008,21 +15846,20 @@ TSynMonitorThroughput = class // process is to be monitored // - this class is thread-safe for its methods, but you should call explicitly // Lock/UnLock to access its individual properties - TSynMonitor = class(TSynPersistent) + TSynMonitor = class(TSynPersistentLock) protected fName: RawUTF8; - fProcessing: boolean; fTaskCount: TSynMonitorCount64; - fInternalErrors: TSynMonitorCount; - fLastInternalError: variant; fTotalTime: TSynMonitorTime; fLastTime: TSynMonitorOneTime; fMinimalTime: TSynMonitorOneTime; fAverageTime: TSynMonitorOneTime; fMaximalTime: TSynMonitorOneTime; fPerSec: QWord; + fInternalErrors: TSynMonitorCount; + fProcessing: boolean; fTaskStatus: (taskNotStarted,taskStarted); - fLock: TRTLCriticalSection; + fLastInternalError: variant; procedure LockedPerSecProperties; virtual; procedure LockedFromProcessTimer; virtual; procedure LockedSum(another: TSynMonitor); virtual; @@ -16041,12 +15878,10 @@ TSynMonitor = class(TSynPersistent) destructor Destroy; override; /// lock the instance for exclusive access // - needed only if you access directly the instance properties - procedure Lock; - {$ifdef HASINLINE}inline;{$endif} + procedure Lock; {$ifdef HASINLINE}inline;{$endif} /// release the instance for exclusive access // - needed only if you access directly the instance properties - procedure UnLock; - {$ifdef HASINLINE}inline;{$endif} + procedure UnLock; {$ifdef HASINLINE}inline;{$endif} /// create Count instances of this actual class in the supplied ObjArr[] class procedure InitializeObjArray(var ObjArr; Count: integer); virtual; /// should be called when the process starts, to resume the internal timer @@ -16090,7 +15925,7 @@ TSynMonitor = class(TSynPersistent) /// returns a TDocVariant with all published properties information // - thread-safe method function ComputeDetails: variant; - {$endif} + {$endif NOVARIANTS} /// used to allow thread safe timing // - by default, the internal TPrecisionTimer is not thread safe: you can // use this method to update the timing from many threads @@ -16220,8 +16055,6 @@ TSynMonitorServer = class(TSynMonitorInputOutput) property CurrentRequestCount: integer read fCurrentRequestCount; end; - {$M-} - /// a list of simple process statistics TSynMonitorObjArray = array of TSynMonitor; @@ -16234,118 +16067,6 @@ TSynMonitorServer = class(TSynMonitorInputOutput) /// class-reference type (metaclass) of a process statistic information TSynMonitorClass = class of TSynMonitor; - /// value object able to gather information about the current system memory - TSynMonitorMemory = class(TSynPersistent) - protected - FAllocatedUsed: TSynMonitorOneSize; - FAllocatedReserved: TSynMonitorOneSize; - FMemoryLoadPercent: integer; - FPhysicalMemoryFree: TSynMonitorOneSize; - FVirtualMemoryFree: TSynMonitorOneSize; - FPagingFileTotal: TSynMonitorOneSize; - FPhysicalMemoryTotal: TSynMonitorOneSize; - FVirtualMemoryTotal: TSynMonitorOneSize; - FPagingFileFree: TSynMonitorOneSize; - fLastMemoryInfoRetrievedTix: cardinal; - procedure RetrieveMemoryInfo; virtual; - function GetAllocatedUsed: TSynMonitorOneSize; - function GetAllocatedReserved: TSynMonitorOneSize; - function GetMemoryLoadPercent: integer; - function GetPagingFileFree: TSynMonitorOneSize; - function GetPagingFileTotal: TSynMonitorOneSize; - function GetPhysicalMemoryFree: TSynMonitorOneSize; - function GetPhysicalMemoryTotal: TSynMonitorOneSize; - function GetVirtualMemoryFree: TSynMonitorOneSize; - function GetVirtualMemoryTotal: TSynMonitorOneSize; - public - /// initialize the class, and its nested TSynMonitorOneSize instances - constructor Create; override; - /// finalize the class, and its nested TSynMonitorOneSize instances - destructor Destroy; override; - /// some text corresponding to current 'free/total' memory information - // - returns e.g. '10.3 GB / 15.6 GB' - class function FreeAsText: RawUTF8; - /// how many physical memory is currently installed, as text (e.g. '32GB'); - class function PhysicalAsText: RawUTF8; - /// returns a JSON object with the current system memory information - // - numbers would be given in KB (Bytes shl 10) - class function ToJSON: RawUTF8; - {$ifndef NOVARIANTS} - /// fill a TDocVariant with the current system memory information - // - numbers would be given in KB (Bytes shl 10) - class function ToVariant: variant; - {$endif} - published - /// Total of allocated memory used by the program - property AllocatedUsed: TSynMonitorOneSize read GetAllocatedUsed; - /// Total of allocated memory reserved by the program - property AllocatedReserved: TSynMonitorOneSize read GetAllocatedReserved; - /// Percent of memory in use for the system - property MemoryLoadPercent: integer read GetMemoryLoadPercent; - /// Total of physical memory for the system - property PhysicalMemoryTotal: TSynMonitorOneSize read GetPhysicalMemoryTotal; - /// Free of physical memory for the system - property PhysicalMemoryFree: TSynMonitorOneSize read GetPhysicalMemoryFree; - /// Total of paging file for the system - property PagingFileTotal: TSynMonitorOneSize read GetPagingFileTotal; - /// Free of paging file for the system - property PagingFileFree: TSynMonitorOneSize read GetPagingFileFree; - {$ifdef MSWINDOWS} - /// Total of virtual memory for the system - // - property not defined under Linux, since not applying to this OS - property VirtualMemoryTotal: TSynMonitorOneSize read GetVirtualMemoryTotal; - /// Free of virtual memory for the system - // - property not defined under Linux, since not applying to this OS - property VirtualMemoryFree: TSynMonitorOneSize read GetVirtualMemoryFree; - {$endif} - end; - - /// value object able to gather information about a system drive - TSynMonitorDisk = class(TSynPersistent) - protected - fName: TFileName; - {$ifdef MSWINDOWS} - fVolumeName: TFileName; - {$endif} - fAvailableSize: TSynMonitorOneSize; - fFreeSize: TSynMonitorOneSize; - fTotalSize: TSynMonitorOneSize; - fLastDiskInfoRetrievedTix: cardinal; - procedure RetrieveDiskInfo; virtual; - function GetName: TFileName; - function GetAvailable: TSynMonitorOneSize; - function GetFree: TSynMonitorOneSize; - function GetTotal: TSynMonitorOneSize; - public - /// initialize the class, and its nested TSynMonitorOneSize instances - constructor Create; override; - /// finalize the class, and its nested TSynMonitorOneSize instances - destructor Destroy; override; - /// some text corresponding to current 'free/total' disk information - // - could return e.g. 'D: 64.4 GB / 213.4 GB' - class function FreeAsText: RawUTF8; - published - /// the disk name - property Name: TFileName read GetName; - {$ifdef MSWINDOWS} - /// the volume name (only available on Windows) - property VolumeName: TFileName read fVolumeName write fVolumeName; - {$endif MSWINDOWS} - /// space currently available on this disk for the current user - // - may be less then FreeSize, if user quotas are specified - property AvailableSize: TSynMonitorOneSize read GetAvailable; - /// free space currently available on this disk - property FreeSize: TSynMonitorOneSize read GetFree; - /// total space - property TotalSize: TSynMonitorOneSize read GetTotal; - end; - -/// retrieve low-level information about a given disk partition -// - as used by TSynMonitorDisk -procedure GetDiskInfo(var aDriveFolderOrFile: TFileName; - out aAvailableBytes, aFreeBytes, aTotalBytes: QWord - {$ifdef MSWINDOWS}; aVolumeName: PFileName = nil{$endif}); - { ******************* cross-cutting classes and functions ***************** } @@ -16505,7 +16226,7 @@ TAutoFree = class(TInterfacedObject,IAutoFree) destructor Destroy; override; end; - {$ifdef DELPHI5OROLDER} // IAutoLocker -> internal error C3517 under Delphi 5 :( +{$ifdef DELPHI5OROLDER} // IAutoLocker -> internal error C3517 under Delphi 5 :( TAutoLocker = class protected fSafe: TSynLocker; @@ -16520,7 +16241,7 @@ TAutoLocker = class property Locker: TSynLocker read fSafe; end; IAutoLocker = TAutoLocker; - {$else} +{$else DELPHI5OROLDER} /// an interface used by TAutoLocker to protect multi-thread execution IAutoLocker = interface ['{97559643-6474-4AD3-AF72-B9BB84B4955D}'] @@ -16578,6 +16299,8 @@ TAutoLocker = class // as IAutoLocker so that this class may be automatically injected // - you may use the inherited TAutoLockerDebug class, as defined in SynLog.pas, // to debug unexpected race conditions due to such critical sections + // - consider inherit from high-level TSynPersistentLock or call low-level + // fSafe := NewSynLocker / fSafe^.DoneAndFreemem instead TAutoLocker = class(TInterfacedObjectWithCustomCreate,IAutoLocker) protected fSafe: TSynLocker; @@ -16634,119 +16357,8 @@ TAutoLocker = class(TInterfacedObjectWithCustomCreate,IAutoLocker) // - faster than IAutoLocker.Safe function property Locker: TSynLocker read fSafe; end; - {$endif DELPHI5OROLDER} - - /// the current state of a TBlockingProcess instance - TBlockingEvent = (evNone,evWaiting,evTimeOut,evRaised); - - {$M+} - /// a semaphore used to wait for some process to be finished - // - used e.g. by TBlockingCallback in mORMot.pas - // - once created, process would block via a WaitFor call, which would be - // released when NotifyFinished is called by the process background thread - TBlockingProcess = class(TEvent) - protected - fTimeOutMs: integer; - fEvent: TBlockingEvent; - fSafe: PSynLocker; - fOwnedSafe: TAutoLocker; - procedure ResetInternal; virtual; // override to reset associated params - public - /// initialize the semaphore instance - // - specify a time out millliseconds period after which blocking execution - // should be handled as failure (if 0 is set, default 3000 would be used) - // - an associated mutex shall be supplied - constructor Create(aTimeOutMs: integer; const aSafe: TSynLocker); reintroduce; overload; virtual; - /// initialize the semaphore instance - // - specify a time out millliseconds period after which blocking execution - // should be handled as failure (if 0 is set, default 3000 would be used) - // - an associated mutex would be created and owned by this instance - constructor Create(aTimeOutMs: integer); reintroduce; overload; virtual; - /// finalize the instance - destructor Destroy; override; - /// called to wait for NotifyFinished() to be called, or trigger timeout - // - returns the final state of the process, i.e. evRaised or evTimeOut - function WaitFor: TBlockingEvent; reintroduce; overload; virtual; - /// called to wait for NotifyFinished() to be called, or trigger timeout - // - returns the final state of the process, i.e. evRaised or evTimeOut - function WaitFor(TimeOutMS: integer): TBlockingEvent; reintroduce; overload; - /// should be called by the background process when it is finished - // - the caller would then let its WaitFor method return - // - returns TRUE on success (i.e. status was not evRaised or evTimeout) - // - if the instance is already locked (e.g. when retrieved from - // TBlockingProcessPool.FromCallLocked), you may set alreadyLocked=TRUE - function NotifyFinished(alreadyLocked: boolean=false): boolean; virtual; - /// just a wrapper to reset the internal Event state to evNone - // - may be used to re-use the same TBlockingProcess instance, after - // a successfull WaitFor/NotifyFinished process - // - returns TRUE on success (i.e. status was not evWaiting), setting - // the current state to evNone, and the Call property to 0 - // - if there is a WaitFor currently in progress, returns FALSE - function Reset: boolean; virtual; - /// just a wrapper around fSafe^.Lock - procedure Lock; - /// just a wrapper around fSafe^.Unlock - procedure Unlock; - published - /// the current state of process - // - use Reset method to re-use this instance after a WaitFor process - property Event: TBlockingEvent read fEvent; - /// the time out period, in ms, as defined at constructor level - property TimeOutMs: integer read fTimeOutMS; - end; - {$M-} - - /// used to identify each TBlockingProcessPool call - // - allow to match a given TBlockingProcessPoolItem semaphore - TBlockingProcessPoolCall = type integer; - - /// a semaphore used in the TBlockingProcessPool - // - such semaphore have a Call field to identify each execution - TBlockingProcessPoolItem = class(TBlockingProcess) - protected - fCall: TBlockingProcessPoolCall; - procedure ResetInternal; override; - published - /// an unique identifier, when owned by a TBlockingProcessPool - // - Reset would restore this field to its 0 default value - property Call: TBlockingProcessPoolCall read fCall; - end; +{$endif DELPHI5OROLDER} - /// class-reference type (metaclass) of a TBlockingProcess - TBlockingProcessPoolItemClass = class of TBlockingProcessPoolItem; - - /// manage a pool of TBlockingProcessPoolItem instances - // - each call will be identified via a TBlockingProcessPoolCall unique value - // - to be used to emulate e.g. blocking execution from an asynchronous - // event-driven DDD process - // - it would also allow to re-use TEvent system resources - TBlockingProcessPool = class(TSynPersistent) - protected - fClass: TBlockingProcessPoolItemClass; - fPool: TObjectListLocked; - fCallCounter: TBlockingProcessPoolCall; // set TBlockingProcessPoolItem.Call - public - /// initialize the pool, for a given implementation class - constructor Create(aClass: TBlockingProcessPoolItemClass=nil); reintroduce; - /// finalize the pool - // - would also force all pending WaitFor to trigger a evTimeOut - destructor Destroy; override; - /// book a TBlockingProcess from the internal pool - // - returns nil on error (e.g. the instance is destroying) - // - or returns the blocking process instance corresponding to this call; - // its Call property would identify the call for the asynchronous callback, - // then after WaitFor, the Reset method should be run to release the mutex - // for the pool - function NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem; virtual; - /// retrieve a TBlockingProcess from its call identifier - // - may be used e.g. from the callback of the asynchronous process - // to set some additional parameters to the inherited TBlockingProcess, - // then call NotifyFinished to release the caller WaitFor - // - if leavelocked is TRUE, the returned instance would be locked: caller - // should execute result.Unlock or NotifyFinished(true) after use - function FromCall(call: TBlockingProcessPoolCall; - locked: boolean=false): TBlockingProcessPoolItem; virtual; - end; {$ifndef DELPHI5OROLDER} // internal error C3517 under Delphi 5 :( {$ifndef NOVARIANTS} @@ -16893,155 +16505,6 @@ TLockedDocVariant = class(TInterfacedObjectWithCustomCreate,ILockedDocVariant) {$endif} type - TSynBackgroundThreadProcess = class; - - /// event callback executed periodically by TSynBackgroundThreadProcess - // - Event is wrTimeout after the OnProcessMS waiting period - // - Event is wrSignaled if ProcessEvent.SetEvent has been called - TOnSynBackgroundThreadProcess = procedure(Sender: TSynBackgroundThreadProcess; - Event: TWaitResult) of object; - - /// TThread able to run a method at a given periodic pace - TSynBackgroundThreadProcess = class(TSynBackgroundThreadAbstract) - protected - fOnProcess: TOnSynBackgroundThreadProcess; - fOnException: TNotifyEvent; - fOnProcessMS: cardinal; - fStats: TSynMonitor; - procedure ExecuteLoop; override; - public - /// initialize the thread for a periodic task processing - // - aOnProcess would be called when ProcessEvent.SetEvent is called or - // aOnProcessMS milliseconds period was elapse since last process - // - if aOnProcessMS is 0, will wait until ProcessEvent.SetEvent is called - // - you could define some callbacks to nest the thread execution, e.g. - // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread - constructor Create(const aThreadName: RawUTF8; - aOnProcess: TOnSynBackgroundThreadProcess; aOnProcessMS: cardinal; - aOnBeforeExecute: TNotifyThreadEvent=nil; - aOnAfterExecute: TNotifyThreadEvent=nil; - aStats: TSynMonitorClass=nil; CreateSuspended: boolean=false); reintroduce; virtual; - /// finalize the thread - destructor Destroy; override; - /// access to the implementation event of the periodic task - property OnProcess: TOnSynBackgroundThreadProcess read fOnProcess; - /// event callback executed when OnProcess did raise an exception - // - supplied Sender parameter is the raised Exception instance - property OnException: TNotifyEvent read fOnException write fOnException; - published - /// access to the delay, in milliseconds, of the periodic task processing - property OnProcessMS: cardinal read fOnProcessMS write fOnProcessMS; - /// processing statistics - // - may be nil if aStats was nil in the class constructor - property Stats: TSynMonitor read fStats; - end; - - TSynBackgroundTimer = class; - - /// event callback executed periodically by TSynBackgroundThreadProcess - // - Event is wrTimeout after the OnProcessMS waiting period - // - Event is wrSignaled if ProcessEvent.SetEvent has been called - // - Msg is '' if there is no pending message in this task FIFO - // - Msg is set for each pending message in this task FIFO - TOnSynBackgroundTimerProcess = procedure(Sender: TSynBackgroundTimer; - Event: TWaitResult; const Msg: RawUTF8) of object; - - /// used by TSynBackgroundTimer internal registration list - TSynBackgroundTimerTask = record - OnProcess: TOnSynBackgroundTimerProcess; - Secs: cardinal; - NextTix: Int64; - FIFO: TRawUTF8DynArray; - end; - /// stores TSynBackgroundTimer internal registration list - TSynBackgroundTimerTaskDynArray = array of TSynBackgroundTimerTask; - - /// TThread able to run one or several tasks at a periodic pace - // - as used e.g. by TSQLRest.TimerEnable/TimerDisable methods, via the - // inherited TSQLRestBackgroundTimer - // - each process can have its own FIFO of text messages - TSynBackgroundTimer = class(TSynBackgroundThreadProcess) - protected - fTask: TSynBackgroundTimerTaskDynArray; - fTasks: TDynArray; - fTaskLock: TSynLocker; - procedure EverySecond(Sender: TSynBackgroundThreadProcess; Event: TWaitResult); - function Find(const aProcess: TMethod): integer; - function Add(aOnProcess: TOnSynBackgroundTimerProcess; - const aMsg: RawUTF8; aExecuteNow: boolean): boolean; - public - /// initialize the thread for a periodic task processing - // - you could define some callbacks to nest the thread execution, e.g. - // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread, as - // made by TSQLRestBackgroundTimer.Create - constructor Create(const aThreadName: RawUTF8; - aOnBeforeExecute: TNotifyThreadEvent=nil; aOnAfterExecute: TNotifyThreadEvent=nil; - aStats: TSynMonitorClass=nil); reintroduce; virtual; - /// finalize the thread - destructor Destroy; override; - /// define a process method for a task running on a periodic number of seconds - // - for background process on a mORMot service, consider using TSQLRest - // TimerEnable/TimerDisable methods, and its associated BackgroundTimer thread - procedure Enable(aOnProcess: TOnSynBackgroundTimerProcess; aOnProcessSecs: cardinal); - /// undefine a task running on a periodic number of seconds - // - aOnProcess should have been registered by a previous call to Enable() method - // - returns true on success, false if the supplied task was not registered - // - for background process on a mORMot service, consider using TSQLRestServer - // TimerEnable/TimerDisable methods, and their TSynBackgroundTimer thread - function Disable(aOnProcess: TOnSynBackgroundTimerProcess): boolean; - /// add a message to be processed during the next execution of a task - // - supplied message will be added to the internal FIFO list associated - // with aOnProcess, then supplied to as aMsg parameter for each call - // - if aExecuteNow is true, won't wait for the next aOnProcessSecs occurence - // - aOnProcess should have been registered by a previous call to Enable() method - // - returns true on success, false if the supplied task was not registered - function EnQueue(aOnProcess: TOnSynBackgroundTimerProcess; - const aMsg: RawUTF8; aExecuteNow: boolean=false): boolean; overload; - /// add a message to be processed during the next execution of a task - // - supplied message will be added to the internal FIFO list associated - // with aOnProcess, then supplied to as aMsg parameter for each call - // - if aExecuteNow is true, won't wait for the next aOnProcessSecs occurence - // - aOnProcess should have been registered by a previous call to Enable() method - // - returns true on success, false if the supplied task was not registered - function EnQueue(aOnProcess: TOnSynBackgroundTimerProcess; - const aMsgFmt: RawUTF8; const Args: array of const; aExecuteNow: boolean=false): boolean; overload; - /// remove a message from the processing list - // - supplied message will be searched in the internal FIFO list associated - // with aOnProcess, then removed from the list if found - // - aOnProcess should have been registered by a previous call to Enable() method - // - returns true on success, false if the supplied message was not registered - function DeQueue(aOnProcess: TOnSynBackgroundTimerProcess; const aMsg: RawUTF8): boolean; - /// execute a task without waiting for the next aOnProcessSecs occurence - // - aOnProcess should have been registered by a previous call to Enable() method - // - returns true on success, false if the supplied task was not registered - function ExecuteNow(aOnProcess: TOnSynBackgroundTimerProcess): boolean; - /// returns true if there is currenly one task processed - function Processing: boolean; - /// wait until no background task is processed - procedure WaitUntilNotProcessing(timeoutsecs: integer=10); - /// low-level access to the internal task list - property Task: TSynBackgroundTimerTaskDynArray read fTask; - /// low-level access to the internal task mutex - property TaskLock: TSynLocker read fTaskLock; - end; - - /// an alternative to TSynPersistentLocked, but via PSynLocker and not TSynLocker - // - TSynLocker will increase inherited fields offset so it may be fine if - // you want to avoid allocation of a PSynLocker buffer, but you may - // prefer this implementation for general-purpose processing class - TSynPersistentLock = class(TSynPersistent) - protected - fSafe: PSynLocker; // TSynLocker will increase inherited fields offset - public - /// initialize the instance, and its associated lock - constructor Create; override; - /// finalize the instance, and its associated lock - destructor Destroy; override; - /// access to the associated instance critical section - // - call Safe.Lock/UnLock to protect multi-thread access on this storage - property Safe: PSynLocker read fSafe; - end; - /// class-reference type (metaclass) of an TSynPersistentLock class TSynPersistentLockClass = class of TSynPersistentLock; @@ -17088,429 +16551,30 @@ TObjectListSorted = class(TSynPersistentLock) property ObjArray: TSynPersistentLockDynArray read fObjArray; end; - /// abstract high-level handling of (SynLZ-)compressed persisted storage - // - LoadFromReader/SaveToWriter abstract methods should be overriden - // with proper binary persistence implementation - TSynPersistentStore = class(TSynPersistentLock) - protected - fName: RawUTF8; - fReader: TFastReader; - fReaderTemp: PRawByteString; - fLoadFromLastUncompressed, fSaveToLastUncompressed: integer; - fLoadFromLastAlgo: TAlgoCompress; - /// low-level virtual methods implementing the persistence reading - procedure LoadFromReader; virtual; - procedure SaveToWriter(aWriter: TFileBufferWriter); virtual; - public - /// initialize a void storage with the supplied name - constructor Create(const aName: RawUTF8); reintroduce; overload; virtual; - /// initialize a storage from a SaveTo persisted buffer - // - raise a EFastReader exception on decoding error - constructor CreateFrom(const aBuffer: RawByteString; - aLoad: TAlgoCompressLoad = aclNormal); - /// initialize a storage from a SaveTo persisted buffer - // - raise a EFastReader exception on decoding error - constructor CreateFromBuffer(aBuffer: pointer; aBufferLen: integer; - aLoad: TAlgoCompressLoad = aclNormal); - /// initialize a storage from a SaveTo persisted buffer - // - raise a EFastReader exception on decoding error - constructor CreateFromFile(const aFileName: TFileName; - aLoad: TAlgoCompressLoad = aclNormal); - /// fill the storage from a SaveTo persisted buffer - // - actually call the LoadFromReader() virtual method for persistence - // - raise a EFastReader exception on decoding error - procedure LoadFrom(const aBuffer: RawByteString; - aLoad: TAlgoCompressLoad = aclNormal); overload; - /// initialize the storage from a SaveTo persisted buffer - // - actually call the LoadFromReader() virtual method for persistence - // - raise a EFastReader exception on decoding error - procedure LoadFrom(aBuffer: pointer; aBufferLen: integer; - aLoad: TAlgoCompressLoad = aclNormal); overload; virtual; - /// initialize the storage from a SaveToFile content - // - actually call the LoadFromReader() virtual method for persistence - // - returns false if the file is not found, true if the file was loaded - // without any problem, or raise a EFastReader exception on decoding error - function LoadFromFile(const aFileName: TFileName; - aLoad: TAlgoCompressLoad = aclNormal): boolean; - /// persist the content as a SynLZ-compressed binary blob - // - to be retrieved later on via LoadFrom method - // - actually call the SaveToWriter() protected virtual method for persistence - // - you can specify ForcedAlgo if you want to override the default AlgoSynLZ - procedure SaveTo(out aBuffer: RawByteString; nocompression: boolean=false; - BufLen: integer=65536; ForcedAlgo: TAlgoCompress=nil); overload; virtual; - /// persist the content as a SynLZ-compressed binary blob - // - just an overloaded wrapper - function SaveTo(nocompression: boolean=false; BufLen: integer=65536; - ForcedAlgo: TAlgoCompress=nil): RawByteString; overload; - {$ifdef HASINLINE}inline;{$endif} - /// persist the content as a SynLZ-compressed binary file - // - to be retrieved later on via LoadFromFile method - // - returns the number of bytes of the resulting file - // - actually call the SaveTo method for persistence - function SaveToFile(const aFileName: TFileName; nocompression: boolean=false; - BufLen: integer=65536; ForcedAlgo: TAlgoCompress=nil): PtrUInt; - /// one optional text associated with this storage - // - you can define this field as published to serialize its value in log/JSON - property Name: RawUTF8 read fName; - /// after a LoadFrom(), contains the uncompressed data size read - property LoadFromLastUncompressed: integer read fLoadFromLastUncompressed; - /// after a SaveTo(), contains the uncompressed data size written - property SaveToLastUncompressed: integer read fSaveToLastUncompressed; - end; - - /// implement binary persistence and JSON serialization (not deserialization) - TSynPersistentStoreJson = class(TSynPersistentStore) - protected - // append "name" -> inherited should add properties to the JSON object - procedure AddJSON(W: TTextWriter); virtual; - public - /// serialize this instance as a JSON object - function SaveToJSON(reformat: TTextWriterJSONFormat = jsonCompact): RawUTF8; - end; - -type - /// 64-bit integer unique identifier, as computed by TSynUniqueIdentifierGenerator - // - they are increasing over time (so are much easier to store/shard/balance - // than UUID/GUID), and contain generation time and a 16-bit process ID - // - mapped by TSynUniqueIdentifierBits memory structure - // - may be used on client side for something similar to a MongoDB ObjectID, - // but compatible with TSQLRecord.ID: TID properties - TSynUniqueIdentifier = type Int64; - - /// 16-bit unique process identifier, used to compute TSynUniqueIdentifier - // - each TSynUniqueIdentifierGenerator instance is expected to have - // its own unique process identifier, stored as a 16 bit integer 1..65535 value - TSynUniqueIdentifierProcess = type word; - - {$A-} - /// map 64-bit integer unique identifier internal memory structure - // - as stored in TSynUniqueIdentifier = Int64 values, and computed by - // TSynUniqueIdentifierGenerator - // - bits 0..14 map a 15-bit increasing counter (collision-free) - // - bits 15..30 map a 16-bit process identifier - // - bits 31..63 map a 33-bit UTC time, encoded as seconds since Unix epoch - {$ifdef UNICODE}TSynUniqueIdentifierBits = record{$else}TSynUniqueIdentifierBits = object{$endif} - public - /// the actual 64-bit storage value - // - in practice, only first 63 bits are used - Value: TSynUniqueIdentifier; - /// 15-bit counter (0..32767), starting with a random value - function Counter: word; - {$ifdef HASINLINE}inline;{$endif} - /// 16-bit unique process identifier - // - as specified to TSynUniqueIdentifierGenerator constructor - function ProcessID: TSynUniqueIdentifierProcess; - {$ifdef HASINLINE}inline;{$endif} - /// low-endian 4-byte value representing the seconds since the Unix epoch - // - time is expressed in Coordinated Universal Time (UTC), not local time - // - it uses in fact a 33-bit resolution, so is "Year 2038" bug-free - function CreateTimeUnix: TUnixTime; - {$ifdef HASINLINE}inline;{$endif} - /// fill this unique identifier structure from its TSynUniqueIdentifier value - // - is just a wrapper around PInt64(@self)^ - procedure From(const AID: TSynUniqueIdentifier); - {$ifdef HASINLINE}inline;{$endif} - {$ifndef NOVARIANTS} - /// convert this identifier as an explicit TDocVariant JSON object - // - returns e.g. - // ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1, - // ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"} - function AsVariant: variant; {$ifdef HASINLINE}inline;{$endif} - /// convert this identifier to an explicit TDocVariant JSON object - // - returns e.g. - // ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1, - // ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"} - procedure ToVariant(out result: variant); - {$endif NOVARIANTS} - /// extract the UTC generation timestamp from the identifier as TDateTime - // - time is expressed in Coordinated Universal Time (UTC), not local time - function CreateDateTime: TDateTime; - {$ifdef HASINLINE}inline;{$endif} - /// extract the UTC generation timestamp from the identifier - // - time is expressed in Coordinated Universal Time (UTC), not local time - function CreateTimeLog: TTimeLog; - {$ifndef DELPHI5OROLDER} - /// compare two Identifiers - function Equal(const Another: TSynUniqueIdentifierBits): boolean; - {$ifdef HASINLINE}inline;{$endif} - {$endif DELPHI5OROLDER} - /// convert the identifier into a 16 chars hexadecimal string - function ToHexa: RawUTF8; - {$ifdef HASINLINE}inline;{$endif} - /// fill this unique identifier back from a 16 chars hexadecimal string - // - returns TRUE if the supplied hexadecimal is on the expected format - // - returns FALSE if the supplied text is invalid - function FromHexa(const hexa: RawUTF8): boolean; - /// fill this unique identifier with a fake value corresponding to a given - // timestamp - // - may be used e.g. to limit database queries on a particular time range - // - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0 - procedure FromDateTime(const aDateTime: TDateTime); - /// fill this unique identifier with a fake value corresponding to a given - // timestamp - // - may be used e.g. to limit database queries on a particular time range - // - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0 - procedure FromUnixTime(const aUnixTime: TUnixTime); - end; - {$A+} - - /// points to a 64-bit integer identifier, as computed by TSynUniqueIdentifierGenerator - // - may be used to access the identifier internals, from its stored - // Int64 or TSynUniqueIdentifier value - PSynUniqueIdentifierBits = ^TSynUniqueIdentifierBits; - - /// a 24 chars cyphered hexadecimal string, mapping a TSynUniqueIdentifier - // - has handled by TSynUniqueIdentifierGenerator.ToObfuscated/FromObfuscated - TSynUniqueIdentifierObfuscated = type RawUTF8; - - /// thread-safe 64-bit integer unique identifier computation - // - may be used on client side for something similar to a MongoDB ObjectID, - // but compatible with TSQLRecord.ID: TID properties, since it will contain - // a 63-bit unsigned integer, following our ORM expectations - // - each identifier would contain a 16-bit process identifier, which is - // supplied by the application, and should be unique for this process at a - // given time - // - identifiers may be obfuscated as hexadecimal text, using both encryption - // and digital signature - TSynUniqueIdentifierGenerator = class(TSynPersistent) - protected - fUnixCreateTime: cardinal; - fLatestCounterOverflowUnixCreateTime: cardinal; - fIdentifier: TSynUniqueIdentifierProcess; - fIdentifierShifted: cardinal; - fLastCounter: cardinal; - fCrypto: array[0..7] of cardinal; // only fCrypto[6..7] are used in practice - fCryptoCRC: cardinal; - fSafe: TSynLocker; - function GetComputedCount: Int64; - public - /// initialize the generator for the given 16-bit process identifier - // - you can supply an obfuscation key, which should be shared for the - // whole system, so that you may use FromObfuscated/ToObfuscated methods - constructor Create(aIdentifier: TSynUniqueIdentifierProcess; - const aSharedObfuscationKey: RawUTF8=''); reintroduce; - /// finalize the generator structure - destructor Destroy; override; - /// return a new unique ID - // - this method is very optimized, and would use very little CPU - procedure ComputeNew(out result: TSynUniqueIdentifierBits); overload; - /// return a new unique ID, type-casted to an Int64 - function ComputeNew: Int64; overload; - {$ifdef HASINLINE}inline;{$endif} - /// return an unique ID matching this generator pattern, at a given timestamp - // - may be used e.g. to limit database queries on a particular time range - procedure ComputeFromDateTime(const aDateTime: TDateTime; out result: TSynUniqueIdentifierBits); - /// return an unique ID matching this generator pattern, at a given timestamp - // - may be used e.g. to limit database queries on a particular time range - procedure ComputeFromUnixTime(const aUnixTime: TUnixTime; out result: TSynUniqueIdentifierBits); - /// map a TSynUniqueIdentifier as 24 chars cyphered hexadecimal text - // - cyphering includes simple key-based encryption and a CRC-32 digital signature - function ToObfuscated(const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated; - /// retrieve a TSynUniqueIdentifier from 24 chars cyphered hexadecimal text - // - any file extension (e.g. '.jpeg') would be first deleted from the - // supplied obfuscated text - // - returns true if the supplied obfuscated text has the expected layout - // and a valid digital signature - // - returns false if the supplied obfuscated text is invalid - function FromObfuscated(const aObfuscated: TSynUniqueIdentifierObfuscated; - out aIdentifier: TSynUniqueIdentifier): boolean; - /// some 32-bit value, derivated from aSharedObfuscationKey as supplied - // to the class constructor - // - FromObfuscated and ToObfuscated methods will validate their hexadecimal - // content with this value to secure the associated CRC - // - may be used e.g. as system-depending salt - property CryptoCRC: cardinal read fCryptoCRC; - /// direct access to the associated mutex - property Safe: TSynLocker read fSafe; - published - /// the process identifier, associated with this generator - property Identifier: TSynUniqueIdentifierProcess read fIdentifier; - /// how many times ComputeNew method has been called - property ComputedCount: Int64 read GetComputedCount; - end; - -type - /// store CPU and RAM usage for a given process - // - as used by TSystemUse class - TSystemUseData = packed record - /// when the data has been sampled - Timestamp: TDateTime; - /// percent of current Kernel-space CPU usage for this process - Kernel: single; - /// percent of current User-space CPU usage for this process - User: single; - /// how many KB of working memory are used by this process - WorkKB: cardinal; - /// how many KB of virtual memory are used by this process - VirtualKB: cardinal; - end; - /// store CPU and RAM usage history for a given process - // - as returned by TSystemUse.History - TSystemUseDataDynArray = array of TSystemUseData; - - /// low-level structure used to compute process memory and CPU usage - {$ifdef UNICODE}TProcessInfo = record{$else}TProcessInfo = object{$endif} - private - fSysPrevIdle, fSysPrevKernel, fSysPrevUser, - fDiffIdle, fDiffKernel, fDiffUser, fDiffTotal: Int64; - public - /// initialize the system/process resource tracking - function Init: boolean; - /// to be called before PerSystem() or PerProcess() iteration - function Start: boolean; - /// percent of current Idle/Kernel/User CPU usage for all processes - function PerSystem(out Idle,Kernel,User: currency): boolean; - /// retrieve CPU and RAM usage for a given process - function PerProcess(PID: cardinal; Now: PDateTime; out Data: TSystemUseData; - var PrevKernel, PrevUser: Int64): boolean; - end; - - /// event handler which may be executed by TSystemUse.BackgroundExecute - // - called just after the measurement of each process CPU and RAM consumption - // - run from the background thread, so should not directly make VCL calls, - // unless BackgroundExecute is run from a VCL timer - TOnSystemUseMeasured = procedure(ProcessID: integer; const Data: TSystemUseData) of object; - - /// internal storage of CPU and RAM usage for one process - TSystemUseProcess = record - ID: integer; - Data: TSystemUseDataDynArray; - PrevKernel: Int64; - PrevUser: Int64; - end; - /// internal storage of CPU and RAM usage for a set of processes - TSystemUseProcessDynArray = array of TSystemUseProcess; - - /// monitor CPU and RAM usage of one or several processes - // - you should execute BackgroundExecute on a regular pace (e.g. every second) - // to gather low-level CPU and RAM information for the given set of processes - // - is able to keep an history of latest sample values - // - use Current class function to access a process-wide instance - TSystemUse = class(TSynPersistent) - protected - fProcess: TSystemUseProcessDynArray; - fProcesses: TDynArray; - fDataIndex: integer; - fProcessInfo: TProcessInfo; - fSafe: TAutoLocker; - fHistoryDepth: integer; - fOnMeasured: TOnSystemUseMeasured; - fTimer: TSynBackgroundTimer; - fUnsubscribeProcessOnAccessError: boolean; - function ProcessIndex(aProcessID: integer): integer; - public - /// a TSynBackgroundThreadProcess compatible event - // - matches TOnSynBackgroundTimerProcess callback signature - // - to be supplied e.g. to a TSynBackgroundTimer.Enable method so that it - // will run every few seconds and retrieve the CPU and RAM use - procedure BackgroundExecute(Sender: TSynBackgroundTimer; - Event: TWaitResult; const Msg: RawUTF8); - /// a VCL's TTimer.OnTimer compatible event - // - to be run every few seconds and retrieve the CPU and RAM use: - // ! tmrSystemUse.Interval := 10000; // every 10 seconds - // ! tmrSystemUse.OnTimer := TSystemUse.Current.OnTimerExecute; - procedure OnTimerExecute(Sender: TObject); - /// track the CPU and RAM usage of the supplied set of Process ID - // - any aProcessID[]=0 will be replaced by the current process ID - // - you can specify the number of sample values for the History() method - // - you should then execute the BackgroundExecute method of this instance - // in a VCL timer or from a TSynBackgroundTimer.Enable() registration - constructor Create(const aProcessID: array of integer; - aHistoryDepth: integer=60); reintroduce; overload; virtual; - /// track the CPU and RAM usage of the current process - // - you can specify the number of sample values for the History() method - // - you should then execute the BackgroundExecute method of this instance - // in a VCL timer or from a TSynBackgroundTimer.Enable() registration - constructor Create(aHistoryDepth: integer=60); reintroduce; overload; virtual; - /// finalize all internal data information - destructor Destroy; override; - /// add a Process ID to the internal tracking list - procedure Subscribe(aProcessID: integer); - /// remove a Process ID from the internal tracking list - function Unsubscribe(aProcessID: integer): boolean; - /// returns the total (Kernel+User) CPU usage percent of the supplied process - // - aProcessID=0 will return information from the current process - // - returns -1 if the Process ID was not registered via Create/Subscribe - function Percent(aProcessID: integer=0): single; overload; - /// returns the Kernel-space CPU usage percent of the supplied process - // - aProcessID=0 will return information from the current process - // - returns -1 if the Process ID was not registered via Create/Subscribe - function PercentKernel(aProcessID: integer=0): single; overload; - /// returns the User-space CPU usage percent of the supplied process - // - aProcessID=0 will return information from the current process - // - returns -1 if the Process ID was not registered via Create/Subscribe - function PercentUser(aProcessID: integer=0): single; overload; - /// returns the total (Work+Paged) RAM use of the supplied process, in KB - // - aProcessID=0 will return information from the current process - // - returns 0 if the Process ID was not registered via Create/Subscribe - function KB(aProcessID: integer=0): cardinal; overload; - /// percent of current Idle/Kernel/User CPU usage for all processes - function PercentSystem(out Idle,Kernel,User: currency): boolean; - /// returns the detailed CPU and RAM usage percent of the supplied process - // - aProcessID=0 will return information from the current process - // - returns -1 if the Process ID was not registered via Create/Subscribe - function Data(out aData: TSystemUseData; aProcessID: integer=0): boolean; overload; - /// returns the detailed CPU and RAM usage percent of the supplied process - // - aProcessID=0 will return information from the current process - // - returns Timestamp=0 if the Process ID was not registered via Create/Subscribe - function Data(aProcessID: integer=0): TSystemUseData; overload; - /// returns total (Kernel+User) CPU usage percent history of the supplied process - // - aProcessID=0 will return information from the current process - // - returns nil if the Process ID was not registered via Create/Subscribe - // - returns the sample values as an array, starting from the last to the oldest - // - you can customize the maximum depth, with aDepth < HistoryDepth - function History(aProcessID: integer=0; aDepth: integer=0): TSingleDynArray; overload; - /// returns total (Kernel+User) CPU usage percent history of the supplied - // process, as a string of two digits values - // - aProcessID=0 will return information from the current process - // - returns '' if the Process ID was not registered via Create/Subscribe - // - you can customize the maximum depth, with aDepth < HistoryDepth - // - the memory history (in MB) can be optionally returned in aDestMemoryMB - function HistoryText(aProcessID: integer=0; aDepth: integer=0; - aDestMemoryMB: PRawUTF8=nil): RawUTF8; - {$ifndef NOVARIANTS} - /// returns total (Kernel+User) CPU usage percent history of the supplied process - // - aProcessID=0 will return information from the current process - // - returns null if the Process ID was not registered via Create/Subscribe - // - returns the sample values as a TDocVariant array, starting from the - // last to the oldest, with two digits precision (as currency values) - // - you can customize the maximum depth, with aDepth < HistoryDepth - function HistoryVariant(aProcessID: integer=0; aDepth: integer=0): variant; - {$endif} - /// access to a global instance, corresponding to the current process - // - its HistoryDepth will be of 60 items - class function Current(aCreateIfNone: boolean=true): TSystemUse; - /// returns detailed CPU and RAM usage history of the supplied process - // - aProcessID=0 will return information from the current process - // - returns nil if the Process ID was not registered via Create/Subscribe - // - returns the sample values as an array, starting from the last to the oldest - // - you can customize the maximum depth, with aDepth < HistoryDepth - function HistoryData(aProcessID: integer=0; aDepth: integer=0): TSystemUseDataDynArray; overload; - /// if any unexisting (e.g. closed/killed) process should be unregistered - // - e.g. if OpenProcess() API call fails - property UnsubscribeProcessOnAccessError: boolean - read fUnsubscribeProcessOnAccessError write fUnsubscribeProcessOnAccessError; - /// how many items are stored internally, and returned by the History() method - property HistoryDepth: integer read fHistoryDepth; - /// executed when TSystemUse.BackgroundExecute finished its measurement - property OnMeasured: TOnSystemUseMeasured read fOnMeasured write fOnMeasured; - /// low-level access to the associated timer running BackgroundExecute - // - equals nil if has been associated to no timer - property Timer: TSynBackgroundTimer read fTimer write fTimer; - end; - /// convert a size to a human readable value power-of-two metric value -// - append EB, PB, TB, GB, MB, KB or B symbol +// - append EB, PB, TB, GB, MB, KB or B symbol with or without preceding space // - for EB, PB, TB, GB, MB and KB, add one fractional digit -procedure KB(bytes: Int64; out result: TShort16); overload; +procedure KB(bytes: Int64; out result: TShort16; nospace: boolean); overload; /// convert a size to a human readable value -// - append EB, PB, TB, GB, MB, KB or B symbol +// - append EB, PB, TB, GB, MB, KB or B symbol with preceding space // - for EB, PB, TB, GB, MB and KB, add one fractional digit function KB(bytes: Int64): TShort16; overload; {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell +/// convert a size to a human readable value +// - append EB, PB, TB, GB, MB, KB or B symbol without preceding space +// - for EB, PB, TB, GB, MB and KB, add one fractional digit +function KBNoSpace(bytes: Int64): TShort16; + {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell + +/// convert a size to a human readable value +// - append EB, PB, TB, GB, MB, KB or B symbol with or without preceding space +// - for EB, PB, TB, GB, MB and KB, add one fractional digit +function KB(bytes: Int64; nospace: boolean): TShort16; overload; + {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell + /// convert a string size to a human readable value // - append EB, PB, TB, GB, MB, KB or B symbol // - for EB, PB, TB, GB, MB and KB, add one fractional digit @@ -17523,20 +16587,20 @@ function KB(const buffer: RawByteString): TShort16; overload; procedure KBU(bytes: Int64; var result: RawUTF8); /// convert a micro seconds elapsed time into a human readable value -// - append 'us', 'ms' or 's' symbol -// - for 'us' and 'ms', add two fractional digits +// - append 'us', 'ms', 's', 'm', 'h' and 'd' symbol for the given value range, +// with two fractional digits function MicroSecToString(Micro: QWord): TShort16; overload; {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell /// convert a micro seconds elapsed time into a human readable value -// - append 'us', 'ms' or 's' symbol -// - for 'us' and 'ms', add two fractional digits +// - append 'us', 'ms', 's', 'm', 'h' and 'd' symbol for the given value range, +// with two fractional digits procedure MicroSecToString(Micro: QWord; out result: TShort16); overload; /// convert an integer value into its textual representation with thousands marked // - ThousandSep is the character used to separate thousands in numbers with // more than three digits to the left of the decimal separator -function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8; +function IntToThousandString(Value: integer; const ThousandSep: TShort4=','): shortstring; /// return the Delphi Compiler Version // - returns 'Delphi 2007' or 'Delphi 2010' e.g. @@ -17697,30 +16761,29 @@ implementation {$ifdef FPC} uses - {$ifdef Linux} - SynFPCLinux, + {$ifdef LINUX} Unix, dynlibs, termio, {$ifdef BSD} - ctypes, sysctl, {$else} Linux, SysCall, - {$endif} - {$endif} - {$ifndef MSWINDOWS} - {$ifdef FPCUSEVERSIONINFO} // should be enabled in Synopse.inc + {$endif BSD} + {$ifdef FPCUSEVERSIONINFO} // to be enabled in Synopse.inc fileinfo, // FPC 3.0 and up - winpeimagereader, // winpe exe info - elfreader, // ELF executables - machoreader, // MACH-O executables - {$endif FPCUSEVERSIONINFO} - {$ifdef ISFPC271} - unixcp, - {$endif} - {$endif MSWINDOWS} + {$ifdef DARWIN} + machoreader, // MACH-O executables + {$else} + elfreader, // ELF executables + {$endif DARWIN} + {$endif FPCUSEVERSIONINFO} + {$ifdef ISFPC271} + unixcp, // for GetSystemCodePage + {$endif} + SynFPCLinux, + {$endif LINUX} SynFPCTypInfo; // small wrapper unit around FPC's TypInfo.pp {$endif FPC} @@ -17765,7 +16828,6 @@ function _LStrLenP(s: pointer): SizeInt; inline; begin // here caller ensured s<>'' result := PSizeInt(PAnsiChar(s)-SizeOf(SizeInt))^; end; - {$endif FPC} @@ -17829,7 +16891,7 @@ function TSynAnsiConvert.AnsiBufferToUnicode(Dest: PWideChar; widestringmanager.Ansi2UnicodeMoveProc(Source, {$ifdef ISFPC27}fCodePage,{$endif}tmp,SourceChars); MoveFast(Pointer(tmp)^,Dest^,length(tmp)*2); - result := Dest+SourceChars; + result := Dest+length(tmp); {$else} {$ifdef KYLIX3} result := Dest; // makes compiler happy @@ -17894,7 +16956,7 @@ function TSynAnsiConvert.AnsiBufferToUTF8(Dest: PUTF8Char; end; // UTF-8 is AT MOST 50% bigger than UTF-16 in bytes in range U+0800..U+FFFF -// see http://stackoverflow.com/a/7008095/458259 -> WideCharCount*3 below +// see http://stackoverflow.com/a/7008095 -> WideCharCount*3 below procedure TSynAnsiConvert.InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal; DestTextWriter: TObject; Escape: TTextWriterKind); @@ -18131,7 +17193,7 @@ function TSynAnsiConvert.Utf8ToAnsiBuffer(const S: RawUTF8; result := UTF8BufferToAnsi(tmp,pointer(s),result)-tmp; if result>=DestSize then result := DestSize-1; - MoveFast(tmp,Dest^,result); + {$ifdef FPC}Move{$else}MoveFast{$endif}(tmp,Dest^,result); end; Dest[result] := #0; end; @@ -18298,7 +17360,7 @@ function TSynAnsiFixedWidth.AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Car 353, 8250, 339, 157, 382, 376); constructor TSynAnsiFixedWidth.Create(aCodePage: cardinal); -var i: integer; +var i: PtrInt; A256: array[0..256] of AnsiChar; U256: array[0..256] of WideChar; // AnsiBufferToUnicode() write a last #0 begin @@ -18337,7 +17399,7 @@ constructor TSynAnsiFixedWidth.Create(aCodePage: cardinal); end; function TSynAnsiFixedWidth.IsValidAnsi(WideText: PWideChar; Length: integer): boolean; -var i: integer; +var i: PtrInt; wc: cardinal; begin result := false; @@ -18545,7 +17607,7 @@ function TSynAnsiUTF8.AnsiBufferToUnicode(Dest: PWideChar; function TSynAnsiUTF8.AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal; NoTrailingZero: boolean): PUTF8Char; begin - MoveFast(Source^,Dest^,SourceChars); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Dest^,SourceChars); if not NoTrailingZero then Dest[SourceChars] := #0; result := Dest+SourceChars; @@ -18599,7 +17661,7 @@ function TSynAnsiUTF8.UnicodeBufferToAnsi(Source: PWideChar; function TSynAnsiUTF8.UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar; begin - MoveFast(Source^,Dest^,SourceChars); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Dest^,SourceChars); result := Dest+SourceChars; end; @@ -18636,7 +17698,7 @@ function TSynAnsiUTF8.AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardin function TSynAnsiUTF16.AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal; NoTrailingZero: boolean): PWideChar; begin - MoveFast(Source^,Dest^,SourceChars); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Dest^,SourceChars); result := Pointer(PtrUInt(Dest)+SourceChars); if not NoTrailingZero then result^ := #0; @@ -18670,7 +17732,7 @@ function TSynAnsiUTF16.UnicodeBufferToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: Cardinal): PAnsiChar; begin SourceChars := SourceChars shl 1; // from WideChar count to byte count - MoveFast(Source^,Dest^,SourceChars); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Dest^,SourceChars); result := Dest+SourceChars; end; @@ -18685,76 +17747,63 @@ function TSynAnsiUTF16.UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char; procedure TSynTempBuffer.Init(const Source: RawByteString); begin - len := length(Source); - if len=0 then - buf := nil else begin - if lennil then begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,buf^,len); + PPtrInt(PAnsiChar(buf)+len)^ := 0; // init last 4/8 bytes (makes valgrid happy) + end; + end; end; function TSynTempBuffer.Init: integer; begin buf := @tmp; - result := SizeOf(tmp)-1; + result := SizeOf(tmp)-16; len := result; end; -function TSynTempBuffer.InitRandom(RandomLen: integer): pointer; +function TSynTempBuffer.InitRandom(RandomLen: integer; forcegsl: boolean): pointer; begin - result := Init(RandomLen+3); + Init(nil,RandomLen); if RandomLen>0 then - FillRandom(result,(RandomLen shr 2)+1); + FillRandom(buf,(RandomLen shr 2)+1,forcegsl); + result := buf; end; function TSynTempBuffer.InitIncreasing(Count, Start: integer): PIntegerArray; begin - result := Init((Count-Start)*4); - FillIncreasing(result,Start,Count); + Init(nil,(Count-Start)*4); + FillIncreasing(buf,Start,Count); + result := buf; end; function TSynTempBuffer.InitZero(ZeroLen: integer): pointer; begin - result := Init(ZeroLen); - FillCharFast(result^,ZeroLen,0); + Init(nil,ZeroLen-16); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(buf^,ZeroLen,0); + result := buf; end; procedure TSynTempBuffer.Done; @@ -18778,7 +17827,7 @@ procedure TSynTempBuffer.Done(EndBuf: pointer; var Dest: RawUTF8); procedure TSynTempWriter.Init(maxsize: integer); begin if maxsize<=0 then - maxsize := SizeOf(tmp.tmp)-1; // -1 for trailing #0 + maxsize := SizeOf(tmp.tmp)-16; // TSynTempBuffer allocates +16 pos := tmp.Init(maxsize); end; @@ -18801,7 +17850,7 @@ procedure TSynTempWriter.wr(const val; len: integer); begin if pos-tmp.buf+len>tmp.len then raise ESynException.CreateUTF8('TSynTempWriter(%) overflow',[tmp.len]); - MoveFast(val,pos^,len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(val,pos^,len); inc(pos,len); end; @@ -18839,7 +17888,7 @@ function TSynTempWriter.wrfillchar(count: integer; value: byte): PAnsiChar; begin if pos-tmp.buf+count>tmp.len then raise ESynException.CreateUTF8('TSynTempWriter(%) overflow',[tmp.len]); - FillCharFast(pos^,count,value); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(pos^,count,value); result := pos; inc(pos,count); end; @@ -18934,7 +17983,7 @@ function TRawUTF8InterningSlot.Clean(aMaxRefCount: integer): integer; {$else} if PInteger(s^-8)^<=aMaxRefCount then begin PRawUTF8(s)^ := ''; - {$endif} + {$endif FPC} inc(result); end else begin if s<>d then begin @@ -19325,12 +18374,12 @@ function UTF8ToWinPChar(dest: PAnsiChar; source: PUTF8Char; count: integer): int function ShortStringToAnsi7String(const source: shortstring): RawByteString; begin - SetString(result,PAnsiChar(@source[1]),ord(source[0])); + FastSetString(RawUTF8(result),@source[1],ord(source[0])); end; procedure ShortStringToAnsi7String(const source: shortstring; var result: RawUTF8); begin - SetString(result,PAnsiChar(@source[1]),ord(source[0])); + FastSetString(result,@source[1],ord(source[0])); end; procedure UTF8ToShortString(var dest: shortstring; source: PUTF8Char); @@ -19547,6 +18596,34 @@ function IsValidUTF8(source: PUTF8Char): Boolean; result := true; end; +function IsValidUTF8(const source: RawUTF8): Boolean; +begin + result := IsValidUTF8(pointer(Source),length(Source)); +end; + +function IsValidUTF8(source: PUTF8Char; sourcelen: PtrInt): Boolean; +var extra, i: integer; + c: cardinal; +begin + result := false; + inc(sourcelen,PtrInt(source)); + if source<>nil then + while PtrInt(source)0 then begin + extra := UTF8_EXTRABYTES[c]; + if extra=0 then exit else // invalid leading byte + for i := 1 to extra do + if (PtrInt(source)>=sourcelen) or (byte(source^) and $c0<>$80) then + exit else + inc(source); // check valid UTF-8 content + end; + end; + result := true; +end; + function IsValidUTF8WithoutControlChars(source: PUTF8Char): Boolean; var extra, i: integer; c: cardinal; @@ -20094,14 +19171,14 @@ function ToUTF8({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} guid: TGUI GUIDToText(pointer(result),@guid); end; -procedure Int32ToUTF8(Value: integer; var result: RawUTF8); -var tmp: array[0..15] of AnsiChar; +procedure Int32ToUTF8(Value: PtrInt; var result: RawUTF8); +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - if cardinal(Value)<=high(SmallUInt32UTF8) then + if PtrUInt(Value)<=high(SmallUInt32UTF8) then result := SmallUInt32UTF8[Value] else begin - P := StrInt32(@tmp[15],Value); - FastSetString(result,P,@tmp[15]-P); + P := StrInt32(@tmp[23],Value); + FastSetString(result,P,@tmp[23]-P); end; end; @@ -20109,9 +19186,12 @@ procedure Int64ToUtf8(Value: Int64; var result: RawUTF8); var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin + {$ifdef CPU64} + if PtrUInt(Value)<=high(SmallUInt32UTF8) then + {$else} // Int64Rec gives compiler internal error C4963 if (PCardinalArray(@Value)^[0]<=high(SmallUInt32UTF8)) and (PCardinalArray(@Value)^[1]=0) then - // Int64Rec gives compiler internal error C4963 + {$endif CPU64} result := SmallUInt32UTF8[Value] else begin P := StrInt64(@tmp[23],Value); FastSetString(result,P,@tmp[23]-P); @@ -20122,8 +19202,12 @@ procedure UInt64ToUtf8(Value: QWord; var result: RawUTF8); var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin + {$ifdef CPU64} + if Value<=high(SmallUInt32UTF8) then + {$else} // Int64Rec gives compiler internal error C4963 if (PCardinalArray(@Value)^[0]<=high(SmallUInt32UTF8)) and (PCardinalArray(@Value)^[1]=0) then + {$endif CPU64} result := SmallUInt32UTF8[Value] else begin P := StrUInt64(@tmp[23],Value); FastSetString(result,P,@tmp[23]-P); @@ -20212,14 +19296,11 @@ function VarRecToTempUTF8(const V: TVarRec; var Res: TTempUTF8): integer; exit; end; vtChar: begin - {$ifdef FPC} // alf: to circumvent FPC issue - RawUnicodeToUtf8(@V.VChar,1,RawUTF8(Res.TempRawUTF8)); - {$else} - Res.Text := @V.VChar; + Res.Temp[0] := V.VChar; // V may be on transient stack (alf: FPC) + Res.Text := @Res.Temp; Res.Len := 1; result := 1; exit; - {$endif} end; vtPWideChar: RawUnicodeToUtf8(V.VPWideChar,StrLenW(V.VPWideChar),RawUTF8(Res.TempRawUTF8)); @@ -20279,7 +19360,7 @@ function VarRecToTempUTF8(const V: TVarRec; var Res: TTempUTF8): integer; vtPointer,vtInterface: begin Res.Text := @Res.Temp; Res.Len := SizeOf(pointer)*2; - BinToHexDisplay(V.VPointer,@Res.Temp,SizeOf(Pointer)); + BinToHexDisplayLower(V.VPointer,@Res.Temp,SizeOf(Pointer)); result := SizeOf(pointer)*2; exit; end; @@ -20303,12 +19384,17 @@ function VarRecToTempUTF8(const V: TVarRec; var Res: TTempUTF8): integer; end; {$ifndef NOVARIANTS} vtVariant: - if VariantToInt64(V.VVariant^,v64) then begin - Res.Text := PUTF8Char(StrInt64(@Res.Temp[23],v64)); - Res.Len := @Res.Temp[23]-Res.Text; - result := Res.Len; - exit; - end else + if VariantToInt64(V.VVariant^,v64) then + if (PCardinalArray(@v64)^[0]<=high(SmallUInt32UTF8)) and + (PCardinalArray(@v64)^[1]=0) then begin + result := v64; + goto smlu32; + end else begin + Res.Text := PUTF8Char(StrInt64(@Res.Temp[23],v64)); + Res.Len := @Res.Temp[23]-Res.Text; + result := Res.Len; + exit; + end else VariantToUTF8(V.VVariant^,RawUTF8(Res.TempRawUTF8),isString); {$endif} else begin @@ -20585,17 +19671,17 @@ procedure UTF8ToSynUnicode(Text: PUTF8Char; Len: integer; var result: SynUnicode end; function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar; -{$ifdef CPU64} -{$ifdef FPC} -begin // fallback to pure pascal version, since asm version below make GPFs for FPC +{$ifdef ABSOLUTEPASCALORNOTINTEL} +begin // fallback to pure pascal version for ARM or PIC if val<0 then begin result := StrUInt32(P,PtrUInt(-val))-1; result^ := '-'; end else result := StrUInt32(P,val); end; -{$else} {$ifdef FPC}nostackframe; assembler; asm {$else} -asm // rcx=P, rdx=val (Linux: rdi,rsi) +{$else} +{$ifdef CPUX64} {$ifdef FPC}nostackframe; assembler; asm {$else} +asm // rcx=P, rdx=val (Linux: rdi,rsi) - val is QWord on 64-bit Intel CPU .noframe {$endif FPC} {$ifndef win64} @@ -20640,16 +19726,6 @@ function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar; mov [rcx - 1], dl lea rax, [rcx + r10 - 1] // includes '-' if val<0 end; -{$endif FPC} -{$else} -{$ifdef PUREPASCAL} -begin // this code is faster than the Borland's original str() or IntToStr() - if val<0 then begin - result := StrUInt32(P,PtrUInt(-val))-1; - result^ := '-'; - end else - result := StrUInt32(P,val); -end; {$else} asm // eax=P, edx=val mov ecx, edx @@ -20699,12 +19775,39 @@ function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar; mov [eax], dl add eax, ecx // includes '-' if val<0 end; -{$endif CPU64} -{$endif PUREPASCAL} +{$endif CPUX64} +{$endif ABSOLUTEPASCALORNOTINTEL} function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar; +{$ifdef ABSOLUTEPASCALORNOTINTEL} // fallback to pure pascal version for ARM or PIC +var c100: PtrUInt; // val/c100 are QWord on 64-bit CPU + tab: PWordArray; +begin // this code is faster than Borland's original str() or IntToStr() + tab := @TwoDigitLookupW; + repeat + if val<10 then begin + dec(P); + P^ := AnsiChar(val+ord('0')); + break; + end else + if val<100 then begin + dec(P,2); + PWord(P)^ := tab[val]; + break; + end; + dec(P,2); + c100 := val div 100; + dec(val,c100*100); + PWord(P)^ := tab[val]; + val := c100; + if c100=0 then + break; + until false; + result := P; +end; +{$else} {$ifdef CPUX64} {$ifdef FPC}nostackframe; assembler; asm {$else} -asm // rcx=P, rdx=val (Linux: rdi,rsi) +asm // rcx=P, rdx=val (Linux: rdi,rsi) - val is QWord on Intel 64-bit CPU .noframe {$endif FPC} {$ifndef win64} @@ -20744,32 +19847,6 @@ function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar; mov [rax], dl end; {$else} -{$ifdef PUREPASCAL} -var c100: PtrUInt; - tab: PWordArray; -begin // this code is faster than the Borland's original str() or IntToStr() - tab := @TwoDigitLookupW; - repeat - if val<10 then begin - dec(P); - P^ := AnsiChar(val+ord('0')); - break; - end else - if val<100 then begin - dec(P,2); - PWord(P)^ := tab[val]; - break; - end; - dec(P,2); - c100 := val div 100; - dec(val,c100*100); - PWord(P)^ := tab[val]; - val := c100; - if c100=0 then break; - until false; - result := P; -end; -{$else} asm // eax=P, edx=val cmp edx, 10 jb @3 // direct process of common val=0 (or val<10) @@ -20807,20 +19884,20 @@ function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar; mov [eax], dl end; {$endif CPU64} -{$endif PUREPASCAL} +{$endif ABSOLUTEPASCALORNOTINTEL} function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar; {$ifdef CPU64} -begin // StrUInt32 aldready implemented PtrUInt=UInt64 - result := StrUInt32(P,val); +begin + result := StrUInt32(P,val); // StrUInt32 converts PtrUInt=QWord on 64-bit CPU end; {$else} var c,c100: QWord; - tab: {$ifdef CPUX86}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; + tab: {$ifdef CPUX86NOTPIC}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; begin - if Int64Rec(val).Hi=0 then - P := StrUInt32(P,Int64Rec(val).Lo) else begin - {$ifndef CPUX86}tab := @TwoDigitLookupW;{$endif} + if PInt64Rec(@val)^.Hi=0 then + P := StrUInt32(P,PCardinal(@val)^) else begin + {$ifndef CPUX86NOTPIC}tab := @TwoDigitLookupW;{$endif} c := val; repeat {$ifdef PUREPASCAL} @@ -20852,9 +19929,9 @@ function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar; dec(P,2); PWord(P)^ := tab[c]; c := c100; - if Int64Rec(c).Hi=0 then begin - if Int64Rec(c).Lo<>0 then - P := StrUInt32(P,Int64Rec(c).Lo); + if PInt64Rec(@c)^.Hi=0 then begin + if PCardinal(@c)^<>0 then + P := StrUInt32(P,PCardinal(@c)^); break; end; until false; @@ -20865,23 +19942,25 @@ function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar; function StrInt64(P: PAnsiChar; const val: Int64): PAnsiChar; begin + {$ifdef CPU64} + result := StrInt32(P,val); // StrInt32 converts PtrInt=Int64 on 64-bit CPU + {$else} if val<0 then begin P := StrUInt64(P,-val)-1; P^ := '-'; end else P := StrUInt64(P,val); result := P; + {$endif CPU64} end; -function IPToCardinal(const aIP: RawUTF8; out aValue: cardinal): boolean; -var P: PUTF8Char; - i,c: cardinal; +function IPToCardinal(P: PUTF8Char; out aValue: cardinal): boolean; +var i,c: cardinal; b: array[0..3] of byte absolute aValue; begin result := false; - if (aIP='') or (aIP='127.0.0.1') then + if (P=nil) or (IdemPChar(P,'127.0.0.1') and (P[9]=#0)) then exit; - P := pointer(aIP); for i := 0 to 3 do begin c := GetNextItemCardinal(P,'.'); if (c>255) or ((i<3) and (P=nil)) then @@ -20891,6 +19970,17 @@ function IPToCardinal(const aIP: RawUTF8; out aValue: cardinal): boolean; result := aValue<>$0100007f; end; +function IPToCardinal(const aIP: RawUTF8; out aValue: cardinal): boolean; +begin + result := IPToCardinal(pointer(aIP),aValue); +end; + +function IPToCardinal(const aIP: RawUTF8): cardinal; +begin + if not IPToCardinal(pointer(aIP),result) then + result := 0; +end; + const // see https://en.wikipedia.org/wiki/Baudot_code Baudot2Char: array[0..63] of AnsiChar = @@ -21009,7 +20099,7 @@ function TrimControlChars(const text: RawUTF8; const controls: TSynAnsicharSet): n := i-1; FastSetString(result,nil,len); P := pointer(result); - MoveFast(pointer(text)^,P^,n); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(text)^,P^,n); for j := i+1 to len do if not(text[j] in controls) then begin P[n] := text[j]; @@ -21052,7 +20142,7 @@ procedure Exchg16(P1,P2: PIntegerArray); {$endif} procedure Exchg(P1,P2: PAnsiChar; count: PtrInt); -{$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} + {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} var i, c: PtrInt; u: AnsiChar; begin @@ -21100,6 +20190,93 @@ procedure Exchg(P1,P2: PAnsiChar; count: PtrInt); end; {$endif} +function GetAllBits(Bits, BitCount: Cardinal): boolean; +begin + if BitCount in [low(ALLBITS_CARDINAL)..high(ALLBITS_CARDINAL)] then begin + BitCount := ALLBITS_CARDINAL[BitCount]; + result := (Bits and BitCount)=BitCount; + end else + result := false; +end; + +// naive code gives the best performance - bts [Bits] has an overhead +function GetBit(const Bits; aIndex: PtrInt): boolean; +begin + result := TIntegerArray(Bits)[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0; +end; + +procedure SetBit(var Bits; aIndex: PtrInt); +begin + TIntegerArray(Bits)[aIndex shr 5] := TIntegerArray(Bits)[aIndex shr 5] + or (1 shl (aIndex and 31)); +end; + +procedure UnSetBit(var Bits; aIndex: PtrInt); +begin + PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5] + and not (1 shl (aIndex and 31)); +end; + +function GetBitPtr(Bits: pointer; aIndex: PtrInt): boolean; +begin + result := PIntegerArray(Bits)[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0; +end; + +procedure SetBitPtr(Bits: pointer; aIndex: PtrInt); +begin + PIntegerArray(Bits)[aIndex shr 5] := PIntegerArray(Bits)[aIndex shr 5] + or (1 shl (aIndex and 31)); +end; + +procedure UnSetBitPtr(Bits: pointer; aIndex: PtrInt); +begin + PIntegerArray(Bits)^[aIndex shr 5] := PIntegerArray(Bits)^[aIndex shr 5] + and not (1 shl (aIndex and 31)); +end; + +function GetBit64(const Bits: Int64; aIndex: PtrInt): boolean; +begin + result := aIndex in TBits64(Bits); +end; + +procedure SetBit64(var Bits: Int64; aIndex: PtrInt); +begin + include(PBits64(@Bits)^,aIndex); +end; + +procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt); +begin + exclude(PBits64(@Bits)^,aIndex); +end; + +function GetBitsCount(const Bits; Count: PtrInt): integer; +const POPCNTDATA: array[0..15+4] of integer = (0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,0,1,3,7); +var P: PByte; + v: PtrUInt; + tab: {$ifdef CPUX86NOTPIC}TIntegerArray absolute POPCNTDATA{$else}PIntegerArray{$endif}; +begin + {$ifndef CPUX86NOTPIC} + tab := @POPCNTDATA; + {$endif CPUX86NOTPIC} + P := @Bits; + result := 0; + while Count>=8 do begin + dec(Count,8); + v := P^; + inc(result,tab[v and $f]); + inc(result,tab[v shr 4]); + inc(P); + end; + v := P^; + if Count>=4 then begin + dec(Count,4); + inc(result,tab[v and $f]); + v := v shr 4; + end; + if Count>0 then + inc(result,tab[v and tab[Count+16]]); +end; + {$ifdef FPC} type @@ -21120,7 +20297,7 @@ procedure Exchg(P1,P2: PAnsiChar; count: PtrInt); tkObject,tkRecord,tkDynArray,tkInterface,tkVariant]; // maps record or object types tkRecordTypes = [tkObject,tkRecord]; - tkRecordTypeOrSet = [tkObject,tkRecord]; + tkRecordKinds = [tkObject,tkRecord]; type // as defined in Delphi 6 and up @@ -21156,7 +20333,7 @@ procedure Exchg(P1,P2: PAnsiChar; count: PtrInt); const // maps record or object types tkRecordTypes = [tkRecord]; - tkRecordTypeOrSet = tkRecord; + tkRecordKinds = tkRecord; {$endif} @@ -21181,7 +20358,20 @@ TStrRec = record refCnt: SizeInt; length: SizeInt; {$else FPC} - TStrRec = packed record + /// map the Delphi/FPC dynamic array header (stored before each instance) + TDynArrayRec = packed record + /// dynamic array reference count (basic garbage memory mechanism) + {$ifdef CPUX64} + _Padding: LongInt; // Delphi/FPC XE2+ expects 16 byte alignment + {$endif} + refCnt: Longint; + /// length in element count + // - size in bytes = length*ElemSize + length: PtrInt; + end; + PDynArrayRec = ^TDynArrayRec; + + TStrRec = packed record {$ifdef UNICODE} {$ifdef CPU64} /// padding bytes for 16 byte alignment of the header @@ -21206,27 +20396,6 @@ TStrRec = record {$endif FPC} end; - /// map the Delphi/FPC dynamic array header (stored before each instance) - TDynArrayRec = packed record - /// dynamic array reference count (basic garbage memory mechanism) - {$ifdef FPC} - refCnt: PtrInt; - high: tdynarrayindex; - function GetLength: sizeint; inline; - procedure SetLength(len: sizeint); inline; - property length: sizeint read GetLength write SetLength; - {$else} - {$ifdef CPUX64} - _Padding: LongInt; // Delphi/FPC XE2+ expects 16 byte alignment - {$endif} - refCnt: Longint; - /// length in element count - // - size in bytes = length*ElemSize - length: PtrInt; - {$endif} - end; - PDynArrayRec = ^TDynArrayRec; - {$ifdef FPC} {$PACKRECORDS C} {$endif FPC} @@ -21235,10 +20404,10 @@ TStrRec = record {$ifdef HASDIRECTTYPEINFO} PTypeInfoStored = PTypeInfo; {$else} - PTypeInfoStored = ^PTypeInfo; + PTypeInfoStored = ^PTypeInfo; // = TypeInfoPtr macro in FPC typinfo.pp {$endif} - // note: FPC TRecInitData (and TInitManagedField) are taken from typinfo.pp + // note: FPC TRecInitData is taken from typinfo.pp via SynFPCTypInfo // since this information is evolving/breaking a lot in the current FPC trunk /// map the Delphi/FPC record field RTTI @@ -21259,7 +20428,7 @@ TStrRec = record /// map the Delphi record field enhanced RTTI (available since Delphi 2010) TEnhancedFieldInfo = packed record TypeInfo: PTypeInfoStored; - Offset: PtrUInt; + Offset: PtrUInt; // match TInitManagedField/TManagedField in FPC typinfo.pp {$ifdef ISDELPHI2010} Flags: Byte; NameLen: byte; // = Name[0] = length(Name) @@ -21359,12 +20528,15 @@ TStrRec = record EnumBaseType: PTypeInfoStored; {$ifdef FPC_ENUMHASINNER} end; - {$endif} + {$endif FPC_ENUMHASINNER} NameList: string[255]; ); tkInteger: ( IntegerType: TOrdType; ); + tkInt64: ( + MinInt64Value, MaxInt64Value: Int64; + ); tkSet: ( SetType: TOrdType; {$ifdef FPC} @@ -21435,7 +20607,8 @@ procedure FastSetStringCP(var s; p: pointer; len, codepage: PtrInt); var r: PAnsiChar; // s may = p -> stand-alone variable sr: PStrRec; // local copy of r, to use register begin - if len>0 then begin + if len<=0 then + r := nil else begin GetMem(r,len+(STRRECSIZE+2)); sr := pointer(r); sr^.codePage := codepage; @@ -21446,35 +20619,32 @@ procedure FastSetStringCP(var s; p: pointer; len, codepage: PtrInt); PWord(PAnsiChar(sr)+len)^ := 0; // ensure ends with two #0 r := pointer(sr); if p<>nil then - MoveFast(p^,sr^,len); - {$ifdef FPC}Finalize(RawByteString(s)){$else}RawByteString(s) := ''{$endif}; - pointer(s) := r; - end - else - {$ifdef FPC}Finalize(RawByteString(s)){$else}RawByteString(s) := ''{$endif}; + {$ifdef FPC}Move{$else}MoveFast{$endif}(p^,sr^,len); + end; + {$ifdef FPC}Finalize(RawByteString(s)){$else}RawByteString(s) := ''{$endif}; + pointer(s) := r; end; procedure FastSetString(var s: RawUTF8; p: pointer; len: PtrInt); var r: PAnsiChar; sr: PStrRec; begin - if len>0 then begin - GetMem(r,len+(STRRECSIZE+2)); + if len<=0 then + r := nil else begin + GetMem(r,len+(STRRECSIZE+4)); sr := pointer(r); sr^.codePage := CP_UTF8; sr^.elemSize := 1; sr^.refCnt := 1; sr^.length := len; inc(sr); - PWord(PAnsiChar(sr)+len)^ := 0; + PCardinal(PAnsiChar(sr)+len)^ := 0; // ends with four #0 r := pointer(sr); if p<>nil then - MoveFast(p^,sr^,len); - {$ifdef FPC}Finalize(s){$else}s := ''{$endif}; - pointer(s) := r; - end - else - {$ifdef FPC}Finalize(s){$else}s := ''{$endif}; + {$ifdef FPC}Move{$else}MoveFast{$endif}(p^,sr^,len); + end; + {$ifdef FPC}Finalize(s){$else}s := ''{$endif}; + pointer(s) := r; end; {$else} procedure FastSetStringCP(var s; p: pointer; len, codepage: PtrInt); @@ -21485,7 +20655,17 @@ procedure FastSetString(var s: RawUTF8; p: pointer; len: PtrInt); begin SetString(RawByteString(s),PAnsiChar(p),len); end; -{$endif} +{$endif HASCODEPAGE} + +procedure GetMemAligned(var s: RawByteString; p: pointer; len: PtrInt; + out aligned: pointer); +begin + SetString(s,nil,len+16); + aligned := pointer(s); + inc(PtrUInt(aligned),PtrUInt(aligned) and 15); + if p<>nil then + {$ifdef FPC}Move{$else}MoveFast{$endif}(p^,aligned^,len); +end; function ToText(k: TTypeKind): PShortString; overload; begin @@ -21665,7 +20845,7 @@ function TypeInfoToName(aTypeInfo: pointer): RawUTF8; function RecordTypeInfoSize(aRecordTypeInfo: pointer): integer; var info: PTypeInfo; begin - info := GetTypeInfo(aRecordTypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(aRecordTypeInfo,tkRecordKinds); if info=nil then result := 0 else result := info^.recSize; @@ -21767,7 +20947,7 @@ procedure GetCaptionFromTrimmed(PS: PShortString; var result: string); inc(PByte(PS)); while (L>0) and (PS^[0] in ['a'..'z']) do begin inc(PByte(PS)); dec(L); end; tmp[L] := #0; // as expected by GetCaptionFromPCharLen/UnCamelCase - MoveFast(PS^,tmp,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(PS^,tmp,L); GetCaptionFromPCharLen(tmp,result); end; @@ -21796,7 +20976,7 @@ function GetEnumName(aTypeInfo: pointer; aIndex: integer): PShortString; dec(aIndex); if aIndex=0 then break; - inc(PByte(result),ord(result^[0])+1); + inc(PByte(result),ord(result^[0])+1); // loop unrolled twice dec(aIndex); if aIndex=0 then break; @@ -21847,23 +21027,40 @@ function GetEnumName(aTypeInfo: pointer; aIndex: integer): PShortString; end; {$endif} +{$ifdef PUREPASCAL} // for proper inlining +function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: PtrInt): boolean; +var i,j: PtrInt; +begin + result := false; + j := 0; + for i := 1 to P1P2Len shr 2 do + if (PCardinalArray(P1)[j] xor PCardinalArray(P2)[j]) and $dfdfdfdf<>0 then + exit else + inc(j); + for i := j*4 to P1P2Len-1 do + if (ord(P1[i]) xor ord(P2[i])) and $df<>0 then + exit; + result := true; +end; +{$endif PUREPASCAL} + function FindShortStringListExact(List: PShortString; MaxValue: integer; - aValue: PUTF8Char; aValueLen: integer): integer; -var PLen: integer; + aValue: PUTF8Char; aValueLen: PtrInt): integer; +var PLen: PtrInt; begin if aValueLen<>0 then for result := 0 to MaxValue do begin PLen := ord(List^[0]); if (PLen=aValuelen) and IdemPropNameUSameLen(@List^[1],aValue,aValueLen) then - exit else - inc(PByte(List),PLen+1); // next short string + exit; + inc(PByte(List),PLen+1); // next short string end; result := -1; end; function FindShortStringListTrimLowerCase(List: PShortString; MaxValue: integer; - aValue: PUTF8Char; aValueLen: integer): integer; -var PLen: integer; + aValue: PUTF8Char; aValueLen: PtrInt): integer; +var PLen: PtrInt; begin if aValueLen<>0 then for result := 0 to MaxValue do begin @@ -21876,8 +21073,8 @@ function FindShortStringListTrimLowerCase(List: PShortString; MaxValue: integer; dec(PLen); until PLen=0; if (PLen=aValueLen) and IdemPropNameUSameLen(aValue,PUTF8Char(List),PLen) then - exit else - inc(PUTF8Char(List),PLen); + exit; + inc(PUTF8Char(List),PLen); end; result := -1; end; @@ -21920,7 +21117,7 @@ function GetSetName(aTypeInfo: pointer; const value): RawUTF8; result := ''; if GetSetInfo(aTypeInfo,max,PS) then begin for i := 0 to max do begin - if GetBit(value,i) then + if GetBitPtr(@value,i) then result := FormatUTF8('%%,',[result,PS^]); inc(PByte(PS),ord(PS^[0])+1); // next short string end; @@ -21941,7 +21138,7 @@ procedure AppendShortComma(text: PAnsiChar; len: integer; var result: shortstrin end; if integer(ord(result[0]))+len>=255 then exit; - MoveFast(text^,result[ord(result[0])+1],len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(text^,result[ord(result[0])+1],len); inc(result[0],len+1); result[ord(result[0])] := ','; end; @@ -21954,7 +21151,7 @@ procedure GetSetNameShort(aTypeInfo: pointer; const value; out result: ShortStri result := ''; if GetSetInfo(aTypeInfo,max,PS) then begin for i := 0 to max do begin - if GetBit(value,i) then + if GetBitPtr(@value,i) then AppendShortComma(@PS^[1],ord(PS^[0]),result,trimlowercase); inc(PByte(PS),ord(PS^[0])+1); // next short string end; @@ -21995,7 +21192,7 @@ function GetSetNameValue(aTypeInfo: pointer; var P: PUTF8Char; if i<0 then i := FindShortStringListTrimLowerCase(names,MaxValue,Text,TextLen); if i>=0 then - SetBit(result,i); + SetBitPtr(@result,i); // unknown enum names (i=-1) would just be ignored until EndOfObject=']'; if P=nil then @@ -22032,7 +21229,7 @@ function VariantToInteger(const V: Variant; var Value: integer): boolean; varShortInt: Value := VShortInt; varWord: Value := VWord; varLongWord: - if (VLongWord>=cardinal(Low(integer))) and (VLongWord<=cardinal(High(integer))) then + if VLongWord<=cardinal(High(integer)) then Value := VLongWord else begin result := false; exit; @@ -22170,7 +21367,11 @@ function VariantToInt64(const V: Variant; var Value: Int64): boolean; {$endif} varByte: Value := VByte; varInteger: Value := VInteger; - varWord64, + varWord64: if VInt64>=0 then + Value := VInt64 else begin + result := false; + exit; + end; varInt64: Value := VInt64; else if SetVariantUnRefSimpleValue(V,tmp) then begin @@ -22278,9 +21479,12 @@ procedure VariantToUTF8(const V: Variant; var result: RawUTF8; varLongWord: UInt32ToUTF8(VLongWord,result); {$endif} - varByte, - varBoolean: + varByte: result := SmallUInt32UTF8[VByte]; + varBoolean: + if VBoolean then + result := SmallUInt32UTF8[1] else + result := SmallUInt32UTF8[0]; varInteger: Int32ToUTF8(VInteger,result); varInt64: @@ -22472,19 +21676,19 @@ function Pos(const substr, str: RawUTF8): Integer; overload; end; function IntToString(Value: integer): string; -var tmp: array[0..15] of AnsiChar; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - P := StrInt32(@tmp[15],Value); - Ansi7ToString(PWinAnsiChar(P),@tmp[15]-P,result); + P := StrInt32(@tmp[23],Value); + Ansi7ToString(PWinAnsiChar(P),@tmp[23]-P,result); end; function IntToString(Value: cardinal): string; -var tmp: array[0..15] of AnsiChar; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - P := StrUInt32(@tmp[15],Value); - Ansi7ToString(PWinAnsiChar(P),@tmp[15]-P,result); + P := StrUInt32(@tmp[23],Value); + Ansi7ToString(PWinAnsiChar(P),@tmp[23]-P,result); end; function IntToString(Value: Int64): string; @@ -22514,33 +21718,42 @@ function Curr64ToString(Value: Int64): string; {$ifdef PUREPASCAL} function IntToString(Value: integer): string; -var tmp: array[0..15] of AnsiChar; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - P := StrInt32(@tmp[15],Value); - SetString(result,P,@tmp[15]-P); + if cardinal(Value)<=high(SmallUInt32UTF8) then + result := SmallUInt32UTF8[Value] else begin + P := StrInt32(@tmp[23],Value); + SetString(result,P,@tmp[23]-P); + end; end; {$else} function IntToString(Value: integer): string; asm jmp Int32ToUTF8 end; -{$endif} +{$endif PUREPASCAL} function IntToString(Value: cardinal): string; -var tmp: array[0..15] of AnsiChar; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - P := StrUInt32(@tmp[15],Value); - SetString(result,P,@tmp[15]-P); + if Value<=high(SmallUInt32UTF8) then + result := SmallUInt32UTF8[Value] else begin + P := StrUInt32(@tmp[23],Value); + SetString(result,P,@tmp[23]-P); + end; end; function IntToString(Value: Int64): string; var tmp: array[0..31] of AnsiChar; P: PAnsiChar; begin - P := StrInt64(@tmp[31],Value); - SetString(result,P,@tmp[31]-P); + if (Value>=0) and (Value<=high(SmallUInt32UTF8)) then + result := SmallUInt32UTF8[Value] else begin + P := StrInt64(@tmp[31],Value); + SetString(result,P,@tmp[31]-P); + end; end; function DoubleToString(Value: Double): string; @@ -22558,8 +21771,8 @@ function Curr64ToString(Value: Int64): string; {$endif UNICODE} -{$ifdef CPUX86} -procedure bswap64array(a,b: PQWordArray; n: integer); +procedure bswap64array(a,b: PQWordArray; n: PtrInt); +{$ifdef CPUX86} {$ifdef FPC}nostackframe; assembler;{$endif} asm push ebx push esi @@ -22578,11 +21791,10 @@ procedure bswap64array(a,b: PQWordArray; n: integer); end; {$else} {$ifdef CPUX64} -procedure bswap64array(a,b: PQWordArray; n: integer); {$ifdef FPC}nostackframe; assembler; asm {$else} asm .noframe // rcx=@a rdx=@b r8=n (Linux: rdi,rsi,rdx) -{$endif} +{$endif FPC} @1: {$ifdef win64} mov rax, qword ptr[rcx] bswap rax @@ -22601,14 +21813,11 @@ procedure bswap64array(a,b: PQWordArray; n: integer); jnz @1 end; {$else} -{$ifdef FPC} -procedure bswap64array(a,b: PQWordArray; n: integer); -var i: integer; +var i: PtrInt; begin for i := 0 to n-1 do - b^[i] := SwapEndian(a^[i]); + b^[i] := {$ifdef FPC}SwapEndian{$else}bswap64{$endif}(a^[i]); end; -{$endif FPC} {$endif CPUX64} {$endif CPUX86} @@ -22660,7 +21869,7 @@ function bswap64(const a: QWord): QWord; bswap eax end; {$else} -function bswap32(a: cardinal): cardinal; {$ifdef HASINLINE}inline;{$endif} +function bswap32(a: cardinal): cardinal; begin result := ((a and $ff)shl 24)or((a and $ff00)shl 8)or ((a and $ff0000)shr 8)or((a and $ff000000)shr 24); @@ -22681,7 +21890,7 @@ function bswap64(const a: QWord): QWord; {$define DEFINED_INT32TOUTF8} -function Int32ToUTF8(Value : integer): RawUtf8; // 3x faster than SysUtils.IntToStr +function Int32ToUTF8(Value : PtrInt): RawUtf8; // 3x faster than SysUtils.IntToStr // from IntToStr32_JOH_IA32_6_a, adapted for Delphi 2009+ asm // eax=Value, edx=@result push ebx @@ -23055,7 +22264,7 @@ function CompareMemSmall(P1, P2: Pointer; Length: PtrInt): Boolean; {$ifdef HASINLINE} procedure FillZero(var dest; count: PtrInt); begin - FillCharFast(dest,count,0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(dest,count,0); end; {$else} procedure FillZero(var dest; count: PtrInt); @@ -23065,190 +22274,11 @@ procedure FillZero(var dest; count: PtrInt); end; {$endif} -function SplitRight(const Str: RawUTF8; SepChar: AnsiChar; LeftStr: PRawUTF8): RawUTF8; -var i: PtrInt; -begin - for i := length(Str) downto 1 do - if Str[i]=SepChar then begin - result := copy(Str,i+1,maxInt); - if LeftStr<>nil then - LeftStr^ := copy(Str,1,i-1); - exit; - end; - result := Str; - if LeftStr<>nil then - LeftStr^ := ''; -end; - -function SplitRights(const Str, SepChar: RawUTF8): RawUTF8; -var i, j, sep: PtrInt; - c: AnsiChar; -begin - sep := length(SepChar); - if sep > 0 then - if sep = 1 then - result := SplitRight(Str,SepChar[1]) else begin - for i := length(Str) downto 1 do begin - c := Str[i]; - for j := 1 to sep do - if c=SepChar[j] then begin - result := copy(Str,i+1,maxInt); - exit; - end; - end; - end; - result := Str; -end; - -function Split(const Str, SepStr: RawUTF8; StartPos: integer): RawUTF8; -var i: integer; -begin - i := PosEx(SepStr,Str,StartPos); - if i>0 then - result := Copy(Str,StartPos,i-StartPos) else - if StartPos=1 then - result := Str else - result := Copy(Str,StartPos,maxInt); -end; - -procedure Split(const Str, SepStr: RawUTF8; var LeftStr, RightStr: RawUTF8; ToUpperCase: boolean); -var i: integer; - tmp: RawUTF8; // may be called as Split(Str,SepStr,Str,RightStr) -begin - i := PosEx(SepStr,Str); - if i=0 then begin - LeftStr := Str; - RightStr := ''; - end else begin - tmp := copy(Str,1,i-1); - RightStr := copy(Str,i+length(SepStr),maxInt); - LeftStr := tmp; - end; - if ToUpperCase then begin - LeftStr := UpperCaseU(LeftStr); - RightStr := UpperCaseU(RightStr); - end; -end; - -function Split(const Str, SepStr: RawUTF8; var LeftStr: RawUTF8; ToUpperCase: boolean=false): RawUTF8; -begin - Split(Str,SepStr,LeftStr,result,ToUpperCase); -end; - -procedure Split(const Str: RawUTF8; const SepStr: array of RawUTF8; - const DestPtr: array of PRawUTF8); -var s,i,j,n: integer; -begin - j := 1; - n := 0; - s := 0; - if high(SepStr)>=0 then - while n<=high(DestPtr) do begin - i := PosEx(SepStr[s],Str,j); - if i=0 then begin - if DestPtr[n]<>nil then - DestPtr[n]^ := copy(Str,j,MaxInt); - inc(n); - break; - end; - if DestPtr[n]<>nil then - DestPtr[n]^ := copy(Str,j,i-j); - inc(n); - if snil then - DestPtr[i]^ := ''; -end; - -function StringReplaceAll(const S, OldPattern, NewPattern: RawUTF8): RawUTF8; - - procedure Process(found: integer); - var oldlen,newlen,i,last,posCount,sharedlen: integer; - pos: TIntegerDynArray; - src,dst: PAnsiChar; - begin - oldlen := length(OldPattern); - newlen := length(NewPattern); - SetLength(pos,64); - pos[0] := found; - posCount := 1; - repeat - found := PosEx(OldPattern,S,found+oldlen); - if found=0 then - break; - AddInteger(pos,posCount,found); - until false; - FastSetString(result,nil,Length(S)+(newlen-oldlen)*posCount); - last := 1; - src := pointer(s); - dst := pointer(result); - for i := 0 to posCount-1 do begin - sharedlen := pos[i]-last; - MoveFast(src^,dst^,sharedlen); - inc(src,sharedlen+oldlen); - inc(dst,sharedlen); - MoveFast(pointer(NewPattern)^,dst^,newlen); - inc(dst,newlen); - last := pos[i]+oldlen; - end; - MoveFast(src^,dst^,length(S)-last+1); - end; - -var j: integer; -begin - if (S='') or (OldPattern='') or (OldPattern=NewPattern) then - result := S else begin - j := PosEx(OldPattern, S, 1); // our PosEx() is faster than Pos() - if j=0 then - result := S else - Process(j); - end; -end; - -function StringReplaceTabs(const Source,TabText: RawUTF8): RawUTF8; - - procedure Process(S,D,T: PAnsiChar; TLen: integer); - begin - repeat - if S^=#0 then - break else - if S^<>#9 then begin - D^ := S^; - inc(D); - inc(S); - end else begin - MoveFast(T^,D^,TLen); - inc(D,TLen); - inc(S); - end; - until false; - end; - -var L,i,n,ttl: PtrInt; -begin - ttl := length(TabText); - L := Length(Source); - n := 0; - if ttl<>0 then - for i := 1 to L do - if Source[i]=#9 then - inc(n); - if n=0 then begin - result := Source; - exit; - end; - SetLength(result,L+n*pred(ttl)); - Process(pointer(Source),pointer(result),pointer(TabText),ttl); -end; - function PosCharAny(Str: PUTF8Char; Characters: PAnsiChar): PUTF8Char; var s: PAnsiChar; c: AnsiChar; begin - if (Str<>nil) and (Characters<>nil) then + if (Str<>nil) and (Characters<>nil) and (Characters^<>#0) then repeat c := Str^; if c=#0 then @@ -23293,7 +22323,7 @@ function IdemPChar2(table: PNormTable; p: PUTF8Char; up: PAnsiChar): boolean; u := up^; if u=#0 then break; - if u<>table^[up[PtrUInt(p)]] then + if table^[up[PtrUInt(p)]]<>u then exit; inc(up); until false; @@ -23302,14 +22332,14 @@ function IdemPChar2(table: PNormTable; p: PUTF8Char; up: PAnsiChar): boolean; function PosI(uppersubstr: PUTF8Char; const str: RawUTF8): PtrInt; var u: AnsiChar; - table: {$ifdef CPUX86}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; + table: {$ifdef CPUX86NOTPIC}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; begin if uppersubstr<>nil then begin - {$ifndef CPUX86}table := @NormToUpperAnsi7;{$endif} + {$ifndef CPUX86NOTPIC}table := @NormToUpperAnsi7;{$endif} u := uppersubstr^; for result := 1 to Length(str) do if table[str[result]]=u then - if {$ifdef CPUX86}IdemPChar({$else}IdemPChar2(table,{$endif} + if {$ifdef CPUX86NOTPIC}IdemPChar({$else}IdemPChar2(table,{$endif} @PUTF8Char(pointer(str))[result],PAnsiChar(uppersubstr)+1) then exit; end; @@ -23318,15 +22348,15 @@ function PosI(uppersubstr: PUTF8Char; const str: RawUTF8): PtrInt; function StrPosI(uppersubstr,str: PUTF8Char): PUTF8Char; var u: AnsiChar; - table: {$ifdef CPUX86}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; + table: {$ifdef CPUX86NOTPIC}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; begin if (uppersubstr<>nil) and (str<>nil) then begin - {$ifndef CPUX86}table := @NormToUpperAnsi7;{$endif} + {$ifndef CPUX86NOTPIC}table := @NormToUpperAnsi7;{$endif} u := uppersubstr^; result := str; while result^<>#0 do begin if table[result^]=u then - if {$ifdef CPUX86}IdemPChar({$else}IdemPChar2(table,{$endif} + if {$ifdef CPUX86NOTPIC}IdemPChar({$else}IdemPChar2(table,{$endif} result+1,PAnsiChar(uppersubstr)+1) then exit; inc(result); @@ -23366,7 +22396,7 @@ procedure AppendBufferToRawUTF8(var Text: RawUTF8; Buffer: pointer; BufferLen: P exit; L := length(Text); SetLength(Text,L+BufferLen); - MoveFast(Buffer^,pointer(PtrInt(Text)+L)^,BufferLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Buffer^,pointer(PtrInt(Text)+L)^,BufferLen); end; procedure AppendBuffersToRawUTF8(var Text: RawUTF8; const Buffers: array of PUTF8Char); @@ -23387,7 +22417,7 @@ procedure AppendBuffersToRawUTF8(var Text: RawUTF8; const Buffers: array of PUTF inc(P,TextLen); for i := 0 to high(Buffers) do if Buffers[i]<>nil then begin - MoveFast(Buffers[i]^,P^,lens[i]); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Buffers[i]^,P^,lens[i]); inc(P,lens[i]); end; end; @@ -23397,7 +22427,7 @@ function AppendRawUTF8ToBuffer(Buffer: PUTF8Char; const Text: RawUTF8): PUTF8Cha begin L := length(Text); if L<>0 then begin - MoveFast(Pointer(Text)^,Buffer^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Pointer(Text)^,Buffer^,L); inc(Buffer,L); end; result := Buffer; @@ -23405,14 +22435,14 @@ function AppendRawUTF8ToBuffer(Buffer: PUTF8Char; const Text: RawUTF8): PUTF8Cha function AppendUInt32ToBuffer(Buffer: PUTF8Char; Value: cardinal): PUTF8Char; var L: integer; - tmp: array[0..15] of AnsiChar; + tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin if Value<=high(SmallUInt32UTF8) then result := AppendRawUTF8ToBuffer(Buffer,SmallUInt32UTF8[Value]) else begin - P := StrUInt32(@tmp[15],Value); - L := @tmp[15]-P; - MoveFast(P^,Buffer^,L); + P := StrUInt32(@tmp[23],Value); + L := @tmp[23]-P; + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,Buffer^,L); result := Buffer+L; end; end; @@ -23465,10 +22495,10 @@ procedure QuotedStr(Text: PUTF8Char; Quote: AnsiChar; var result: RawUTF8); P^ := Quote; inc(P); if n=0 then begin - MoveFast(Text^,P^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Text^,P^,L); inc(P,L); end else begin - MoveFast(Text^,P^,first); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Text^,P^,first); n := first; L := first; goto quot; @@ -23508,26 +22538,24 @@ function GotoEndOfQuotedString(P: PUTF8Char): PUTF8Char; result := P; end; // P^='"' at function return -procedure QuotedStrJSON(const aText: RawUTF8; var result: RawUTF8); -var i: integer; - temp: TTextWriterStackBuffer; +procedure QuotedStrJSON(const aText: RawUTF8; var result: RawUTF8; + const aPrefix, aSuffix: RawUTF8); +var temp: TTextWriterStackBuffer; begin - for i := 1 to length(aText) do - case aText[i] of - #0..#31,'\','"': - with TTextWriter.CreateOwnedStream(temp) do - try - Add('"'); - AddJSONEscape(pointer(aText)); - Add('"'); - SetText(result); - exit; - finally - Free; - end; - end; - // if we reached here, no character needs to be escaped in this string - result := '"'+aText+'"'; + if NeedsJsonEscape(aText) then + with TTextWriter.CreateOwnedStream(temp) do + try + AddString(aPrefix); + Add('"'); + AddJSONEscape(pointer(aText)); + Add('"'); + AddString(aSuffix); + SetText(result); + exit; + finally + Free; + end else + result := aPrefix+'"'+aText+'"'+aSuffix; end; function GotoEndOfJSONString(P: PUTF8Char): PUTF8Char; @@ -23753,32 +22781,24 @@ function Base64MagicCheckAndDecode(Value: PUTF8Char; ValueLen: integer; {$ifndef DEFINED_INT32TOUTF8} -function Int32ToUtf8(Value: integer): RawUTF8; // faster than SysUtils.IntToStr -var tmp: array[0..15] of AnsiChar; +function Int32ToUtf8(Value: PtrInt): RawUTF8; // faster than SysUtils.IntToStr +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - if cardinal(Value)<=high(SmallUInt32UTF8) then + if PtrUInt(Value)<=high(SmallUInt32UTF8) then result := SmallUInt32UTF8[Value] else begin - P := StrInt32(@tmp[15],Value); - FastSetString(result,P,@tmp[15]-P); + P := StrInt32(@tmp[23],Value); + FastSetString(result,P,@tmp[23]-P); end; end; function Int64ToUtf8(Value: Int64): RawUTF8; // faster than SysUtils.IntToStr -var tmp: array[0..23] of AnsiChar; - P: PAnsiChar; begin - if (PCardinalArray(@Value)^[0]<=high(SmallUInt32UTF8)) and - (PCardinalArray(@Value)^[1]=0) then - // Int64Rec gives compiler internal error C4963 - result := SmallUInt32UTF8[Value] else begin - P := StrInt64(@tmp[23],Value); - FastSetString(result,P,@tmp[23]-P); - end; + Int64ToUtf8(Value,result); end; function Trim(const S: RawUTF8): RawUTF8; -var I,L: Integer; +var I,L: PtrInt; begin L := Length(S); I := 1; @@ -23805,38 +22825,38 @@ function ToUTF8(Value: Int64): RawUTF8; {$endif CPU64} function ToUTF8(Value: PtrInt): RawUTF8; -var tmp: array[0..15] of AnsiChar; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin - P := StrInt32(@tmp[15],Value); - FastSetString(result,P,@tmp[15]-P); + P := StrInt32(@tmp[23],Value); + FastSetString(result,P,@tmp[23]-P); end; -function UInt32ToUtf8(Value: cardinal): RawUTF8; -var tmp: array[0..15] of AnsiChar; +function UInt32ToUtf8(Value: PtrUInt): RawUTF8; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin if Value<=high(SmallUInt32UTF8) then result := SmallUInt32UTF8[Value] else begin - P := StrUInt32(@tmp[15],Value); - FastSetString(result,P,@tmp[15]-P); + P := StrUInt32(@tmp[23],Value); + FastSetString(result,P,@tmp[23]-P); end; end; -procedure UInt32ToUtf8(Value: cardinal; var result: RawUTF8); -var tmp: array[0..15] of AnsiChar; +procedure UInt32ToUtf8(Value: PtrUInt; var result: RawUTF8); +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; begin if Value<=high(SmallUInt32UTF8) then result := SmallUInt32UTF8[Value] else begin - P := StrUInt32(@tmp[15],Value); - FastSetString(result,P,@tmp[15]-P); + P := StrUInt32(@tmp[23],Value); + FastSetString(result,P,@tmp[23]-P); end; end; {$ifndef EXTENDEDTOSTRING_USESTR} var // standard FormatSettings (US) - SettingsUS: TFormatSettings; + SettingsUS: TFormatSettings; {$endif} function ExtendedToStringNoExp(var S: ShortString; Value: TSynExtended; @@ -23872,7 +22892,7 @@ function ExtendedToStringNoExp(var S: ShortString; Value: TSynExtended; '9': begin S[prec] := '0'; if ((prec=2) and (S[1]='-')) or (prec=1) then begin - MoveFast(S[prec],S[prec+1],result); + {$ifdef FPC}Move{$else}MoveFast{$endif}(S[prec],S[prec+1],result); S[prec] := '1'; break; end; @@ -23901,6 +22921,14 @@ function ExtendedToString(var S: ShortString; Value: TSynExtended; {$ifdef EXTENDEDTOSTRING_USESTR} var scientificneeded: boolean; valueabs: TSynExtended; +const SINGLE_HI: TSynExtended = 1E9; // for proper Delphi 5 compilation + SINGLE_LO: TSynExtended = 1E-9; + DOUBLE_HI: TSynExtended = 1E14; + DOUBLE_LO: TSynExtended = 1E-14; + {$ifndef CPU64} + EXT_HI: TSynExtended = 1E17; + EXT_LO: TSynExtended = 1E-17; + {$endif} begin if Value=0 then begin s[1] := '0'; @@ -23910,16 +22938,16 @@ function ExtendedToString(var S: ShortString; Value: TSynExtended; scientificneeded := false; valueabs := abs(Value); if Precision<=SINGLE_PRECISION then begin - if (valueabs>1E9) or (valueabs<1E-9) then + if (valueabs>SINGLE_HI) or (valueabsDOUBLE_PRECISION then begin - if (valueabs>1E17) or (valueabs<1E-17) then + if (valueabs>EXT_HI) or (valueabs1E14) or (valueabs<1E-14) then + if (valueabs>DOUBLE_HI) or (valueabsnil then {$ifdef FPC}Finalize(RawUTF8(d^.TempRawUTF8)){$else}RawUTF8(d^.TempRawUTF8) := ''{$endif}; @@ -24080,7 +23104,7 @@ function TFormatUTF8.WriteMax(Dest: PUTF8Char; Max: PtrUInt): PUTF8Char; d := @blocks; repeat if PtrUInt(Dest)+PtrUInt(d^.Len)>Max then begin // avoid buffer overflow - MoveFast(d^.Text^,Dest^,Max-PtrUInt(Dest)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(d^.Text^,Dest^,Max-PtrUInt(Dest)); repeat if d^.TempRawUTF8<>nil then {$ifdef FPC}Finalize(RawUTF8(d^.TempRawUTF8)){$else}RawUTF8(d^.TempRawUTF8) := ''{$endif}; @@ -24089,7 +23113,7 @@ function TFormatUTF8.WriteMax(Dest: PUTF8Char; Max: PtrUInt): PUTF8Char; result := PUTF8Char(Max); exit; end; - MoveFast(d^.Text^,Dest^,d^.Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(d^.Text^,Dest^,d^.Len); inc(Dest,d^.Len); if d^.TempRawUTF8<>nil then {$ifdef FPC}Finalize(RawUTF8(d^.TempRawUTF8)){$else}RawUTF8(d^.TempRawUTF8) := ''{$endif}; @@ -24125,6 +23149,13 @@ procedure FormatShort(const Format: RawUTF8; const Args: array of const; end; end; +function FormatToShort(const Format: RawUTF8; const Args: array of const): shortstring; +var process: TFormatUTF8; +begin // Delphi 5 has troubles compiling overloaded FormatShort() + process.Parse(Format,Args); + result[0] := AnsiChar(process.WriteMax(@result[1],255)-@result[1]); +end; + procedure FormatShort16(const Format: RawUTF8; const Args: array of const; var result: TShort16); var process: TFormatUTF8; @@ -24188,14 +23219,11 @@ function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const; J end; result := ''; tmpN := 0; - FillcharFast(inlin,SizeOf(inlin),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(inlin,SizeOf(inlin),0); L := 0; A := 0; P := 0; F := pointer(Format); - {$ifdef FPC} - try // alf: to circumvent FPC issues - {$endif} while F^<>#0 do begin if F^<>'%' then begin FDeb := F; @@ -24266,18 +23294,13 @@ function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const; J inc(F,2); end; L := {$ifdef FPC}_LStrLen(tmp[i]){$else}PInteger(PtrInt(tmp[i])-SizeOf(integer))^{$endif}; - MoveFast(pointer(tmp[i])^,F^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(tmp[i])^,F^,L); inc(F,L); if i in inlin then begin PWord(F)^ := ord(')')+ord(':')shl 8; inc(F,2); end; end; - {$ifdef FPC} - finally - finalize(tmp); - end; - {$endif} end; function ScanUTF8(P: PUTF8Char; PLen: integer; const fmt: RawUTF8; @@ -24285,6 +23308,7 @@ function ScanUTF8(P: PUTF8Char; PLen: integer; const fmt: RawUTF8; var v,w: PtrInt; F,FEnd,PEnd: PUTF8Char; +label next; begin result := 0; if (fmt='') or (P=nil) or (PLen<=0) or (high(values)<0) then @@ -24323,13 +23347,22 @@ function ScanUTF8(P: PUTF8Char; PLen: integer; const fmt: RawUTF8; exit; 'X': if not GetNextItemHexDisplayToBin(P,values[v],8,#0) then exit; - 's': begin + 's','S': begin w := 0; while (P[w]>' ') and (P+w<=PEnd) do inc(w); - SetString(PShortString(values[v])^,P,w); + if F^='s' then + SetString(PShortString(values[v])^,PAnsiChar(P),w) else + FastSetString(PRawUTF8(values[v])^,P,w); inc(P,w); while (P^<=' ') and (P^<>#0) and (P<=PEnd) do inc(P); end; + 'L': begin + w := 0; + while not(P[w] in [#0,#10,#13]) and (P+w<=PEnd) do inc(w); + FastSetString(PRawUTF8(values[v])^,P,w); + inc(P,w); + end; + '%': goto next; else raise ESynException.CreateUTF8('ScanUTF8: unknown ''%'' specifier [%]',[F^,fmt]); end; inc(result); @@ -24345,7 +23378,7 @@ function ScanUTF8(P: PUTF8Char; PLen: integer; const fmt: RawUTF8; exit; break; end else begin - while (P^<>F^) and (P<=PEnd) do inc(P); +next: while (P^<>F^) and (P<=PEnd) do inc(P); inc(F); inc(P); if (F>=FEnd) or (P>=PEnd) then @@ -24371,7 +23404,7 @@ function RawByteStringArrayConcat(const Values: array of RawByteString): RawByte P := pointer(Result); for i := 0 to high(Values) do begin L := length(Values[i]); - MoveFast(pointer(Values[i])^,P^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Values[i])^,P^,L); inc(P,L); end; end; @@ -24382,7 +23415,7 @@ procedure RawByteStringToBytes(const buf: RawByteString; out bytes: TBytes); L := Length(buf); if L<>0 then begin SetLength(bytes,L); - MoveFast(pointer(buf)^,pointer(bytes)^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(buf)^,pointer(bytes)^,L); end; end; @@ -24660,18 +23693,24 @@ function CompareMem(P1, P2: Pointer; Length: PtrInt): Boolean; result := false; end; -// from Aleksandr Sharahov's PosEx_Sha_Pas_2() +{$ifdef HASINLINE} // to use directly the SubStr/S arguments registers +function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt): PtrInt; +begin + result := PosExPas(pointer(SubStr),pointer(S),Offset); +end; +{$endif HASINLINE} + +// from Aleksandr Sharahov's PosEx_Sha_Pas_2() - refactored for cross-platform function PosExPas(pSub, p: PUTF8Char; Offset: PtrUInt): PtrInt; var len, lenSub: PtrInt; ch: AnsiChar; pStart, pStop: PUTF8Char; -label Loop0, Loop4, TestT, Test0, Test1, Test2, Test3, Test4, +label Loop2, Loop6, TestT, Test0, Test1, Test2, Test3, Test4, AfterTestT, AfterTest0, Ret, Exit; begin - if (p=nil) or (pSub=nil) or (Offset<1) then begin - result := 0; + result := 0; + if (p=nil) or (pSub=nil) or (Offset<1) then goto Exit; - end; {$ifdef FPC} len := _LStrLenP(p); lenSub := _LStrLenP(pSub)-1; @@ -24679,71 +23718,61 @@ function PosExPas(pSub, p: PUTF8Char; Offset: PtrUInt): PtrInt; len := PInteger(p-4)^; lenSub := PInteger(pSub-4)^-1; {$endif} - if (len=pStop then goto Exit; + goto Loop2; +Test4: dec(p,2); +Test2: dec(p,2); + goto Test0; +Test3: dec(p,2); +Test1: dec(p,2); TestT: len := lenSub; if lenSub<>0 then - repeat - if (psub[len]<>p[len+1]) or (psub[len+1]<>p[len+2]) then - goto AfterTestT; - len := len+2; - until len>=0; - p := p+2; + repeat + if (psub[len]<>p[len+1]) or (psub[len+1]<>p[len+2]) then + goto AfterTestT; + inc(len,2); + until len>=0; + inc(p,2); if p<=pStop then goto Ret; - result := 0; goto Exit; -Test4: p := p-2; -Test2: p := p-2; Test0: len := lenSub; if lenSub<>0 then - repeat - if (psub[len]<>p[len]) or (psub[len+1]<>p[len+1]) then - goto AfterTest0; - len := len+2; - until len>=0; + repeat + if (psub[len]<>p[len]) or (psub[len+1]<>p[len+1]) then + goto AfterTest0; + inc(len,2); + until len>=0; inc(p); Ret: result := p-pStart; Exit: end; -{$ifdef HASINLINE} // to use directly the SubStr/S arguments registers -function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt): PtrInt; -begin - result := PosExPas(pointer(SubStr),pointer(S),Offset); -end; -{$endif HASINLINE} - function IdemPropNameU(const P1,P2: RawUTF8): boolean; var L: PtrInt; begin @@ -24753,21 +23782,6 @@ function IdemPropNameU(const P1,P2: RawUTF8): boolean; result := false; end; -function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: PtrInt): boolean; -var i,j: PtrInt; -begin - result := false; - j := 0; - for i := 1 to P1P2Len shr 2 do - if (PCardinalArray(P1)[j] xor PCardinalArray(P2)[j]) and $dfdfdfdf<>0 then - exit else - inc(j); - for i := j*4 to P1P2Len-1 do - if (ord(P1[i]) xor ord(P2[i])) and $df<>0 then - exit; - result := true; -end; - function StrIComp(Str1, Str2: pointer): PtrInt; var C1,C2: PtrInt; lookupper: PByteArray; // better x86-64 / PIC asm generation @@ -24819,17 +23833,20 @@ function StrLenPas(S: pointer): PtrInt; end; function StrCompFast(Str1, Str2: pointer): PtrInt; +var c: byte; begin if Str1<>Str2 then if Str1<>nil then if Str2<>nil then begin - if PByte(Str1)^=PByte(Str2)^ then + c := PByte(Str1)^; + if c=PByte(Str2)^ then repeat - if PByte(Str1)^=0 then break; + if c=0 then break; inc(PByte(Str1)); inc(PByte(Str2)); - until PByte(Str1)^<>PByte(Str2)^; - result := PByte(Str1)^-PByte(Str2)^; + c := PByte(Str1)^; + until c<>PByte(Str2)^; + result := c-PByte(Str2)^; exit; end else result := 1 else // Str2='' @@ -24847,6 +23864,14 @@ procedure YearToPChar(Y: PtrUInt; P: PUTF8Char); PWordArray(P)[1] := tab[Y-(d100*100)]; end; +procedure YearToPChar2(tab: PWordArray; Y: PtrUInt; P: PUTF8Char); {$ifdef HASINLINE}inline;{$endif} +var d100: PtrUInt; +begin + d100 := Y div 100; + PWordArray(P)[0] := tab[d100]; + PWordArray(P)[1] := tab[Y-(d100*100)]; +end; + function Iso8601ToTimeLog(const S: RawByteString): TTimeLog; begin result := Iso8601ToTimeLogPUTF8Char(pointer(S),length(S)); @@ -24938,15 +23963,15 @@ function crc32cfast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; inc(buf); until len=0; if len>=4 then - repeat - result := result xor PCardinal(buf)^; - inc(buf,4); - dec(len,4); - result := tab[3,ToByte(result)] xor - tab[2,ToByte(result shr 8)] xor - tab[1,ToByte(result shr 16)] xor - tab[0,result shr 24]; - until len<4; + repeat + result := result xor PCardinal(buf)^; + inc(buf,4); + dec(len,4); + result := tab[3,ToByte(result)] xor + tab[2,ToByte(result shr 8)] xor + tab[1,ToByte(result shr 16)] xor + tab[0,result shr 24]; + until len<4; while len>0 do begin result := tab[0,ToByte(result xor ord(buf^))] xor (result shr 8); dec(len); @@ -25032,6 +24057,15 @@ function SortDynArrayQWord(const A,B): integer; result := 0; end; +function CompareQWord(A, B: QWord): integer; +begin + if AB then + result := 1 else + result := 0; +end; + function SortDynArrayAnsiString(const A,B): integer; begin result := StrComp(pointer(A),pointer(B)); @@ -25078,8 +24112,7 @@ function SortDynArrayPUTF8Char(const A,B): integer; {$else PUREPASCAL} function IdemPChar(p: PUTF8Char; up: PAnsiChar): boolean; -// if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper) -// eax=p edx=up +{$ifdef FPC}nostackframe; assembler;{$endif} asm test eax, eax jz @e // P=nil -> false @@ -25119,6 +24152,7 @@ function IdemPChar(p: PUTF8Char; up: PAnsiChar): boolean; end; function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): PtrInt; +{$ifdef FPC}nostackframe; assembler;{$endif} asm push eax call IntegerScan @@ -25132,6 +24166,7 @@ function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): Pt end; function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardinal; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=P, edx=Count, Value=ecx test eax, eax jz @ok0 // avoid GPF @@ -25207,6 +24242,7 @@ function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardin end; function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=P, edx=Count, Value=ecx test eax, eax jz @z // avoid GPF @@ -25256,6 +24292,7 @@ function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): b end; function PosChar(Str: PUTF8Char; Chr: AnsiChar): PUTF8Char; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // faster version by AB - eax=Str dl=Chr test eax, eax jz @z @@ -25287,6 +24324,7 @@ function PosChar(Str: PUTF8Char; Chr: AnsiChar): PUTF8Char; end; function CompareMem(P1, P2: Pointer; Length: PtrInt): Boolean; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=P1 edx=P2 ecx=Length cmp eax, edx je @0 // P1=P2 @@ -25353,6 +24391,7 @@ function CompareMem(P1, P2: Pointer; Length: PtrInt): Boolean; end; function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt): integer; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=SubStr, edx=S, ecx=Offset push ebx push esi @@ -25430,6 +24469,7 @@ function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt): integer; end; function IdemPropNameU(const P1,P2: RawUTF8): boolean; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=p1, edx=p2 cmp eax, edx je @out1 @@ -25468,6 +24508,7 @@ function IdemPropNameU(const P1,P2: RawUTF8): boolean; end; function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: PtrInt): boolean; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=p1, edx=p2, ecx=P1P2Len cmp eax, edx je @out2 @@ -25505,6 +24546,7 @@ function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: PtrInt): boolean; end; function StrIComp(Str1, Str2: pointer): PtrInt; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // faster version by AB, from Agner Fog's original mov ecx, eax test eax, edx @@ -25557,6 +24599,7 @@ function StrIComp(Str1, Str2: pointer): PtrInt; end; function StrLenPas(S: pointer): PtrInt; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // slower than x86/SSE* StrLen(), but won't read any byte beyond the string mov edx, eax test eax, eax @@ -25581,6 +24624,7 @@ function StrLenPas(S: pointer): PtrInt; end; function StrCompFast(Str1, Str2: pointer): PtrInt; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // no branch taken in case of not equal first char cmp eax, edx je @zero // same string or both nil @@ -25614,6 +24658,7 @@ function StrCompFast(Str1, Str2: pointer): PtrInt; NEGATIVE_POLARITY = 16; function StrCompSSE42(Str1, Str2: pointer): PtrInt; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // warning: may read up to 15 bytes beyond the string itself test eax, edx jz @n @@ -25658,6 +24703,7 @@ function StrCompSSE42(Str1, Str2: pointer): PtrInt; end; function SortDynArrayAnsiStringSSE42(const A,B): integer; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // warning: may read up to 15 bytes beyond the string itself mov eax, [eax] mov edx, [edx] @@ -25704,6 +24750,36 @@ function SortDynArrayAnsiStringSSE42(const A,B): integer; sub eax, edx end; +function StrLenSSE42(S: pointer): PtrInt; +{$ifdef FPC}nostackframe; assembler;{$endif} +asm // warning: may read up to 15 bytes beyond the string itself + mov edx, eax // copy pointer + test eax, eax + jz @null // returns 0 if S=nil + xor eax, eax + {$ifdef HASAESNI} + pxor xmm0, xmm0 + pcmpistri xmm0, dqword[edx], EQUAL_EACH // comparison result in ecx + {$else} + db $66, $0F, $EF, $C0 + db $66, $0F, $3A, $63, $02, EQUAL_EACH + {$endif} + jnz @loop + mov eax, ecx + ret + nop // for @loop alignment +@loop: add eax, 16 + {$ifdef HASAESNI} + pcmpistri xmm0, dqword[edx + eax], EQUAL_EACH // comparison result in ecx + {$else} + db $66, $0F, $3A, $63, $04, $10, EQUAL_EACH + {$endif} + jnz @loop +@ok: add eax, ecx + ret +@null: db $f3 // rep ret +end; + procedure YearToPChar(Y: PtrUInt; P: PUTF8Char); asm // eax=Y, edx=P push edx @@ -26158,6 +25234,19 @@ function SortDynArrayInt64(const A,B): integer; @p: mov eax, 1 end; +function CompareQWord(A, B: QWord): integer; +begin + {$ifdef FPC_OR_UNICODE} // recent compilers are able to generate correct code + if AB then + result := 1 else + result := 0; + {$else} + result := SortDynArrayQWord(A,B); // use correct x86 asm version below + {$endif} +end; + function SortDynArrayQWord(const A,B): integer; asm // Delphi x86 compiler is not efficient, and oldest even incorrect mov ecx, [eax] @@ -26252,6 +25341,199 @@ function SortDynArrayPUTF8Char(const A,B): integer; {$endif PUREPASCAL} +function PosExChar(Chr: AnsiChar; const Str: RawUTF8): PtrInt; +begin + {$ifdef FPC} + if Str<>'' then // // will use fast FPC SSE version + result := IndexByte(pointer(Str)^,_LStrLen(Str),byte(chr))+1 else + {$else} + if Str<>'' then + for result := 1 to PInteger(PtrInt(Str)-sizeof(Integer))^ do + if Str[result]=Chr then + exit; + {$endif FPC} + result := 0; +end; + +function SplitRight(const Str: RawUTF8; SepChar: AnsiChar; LeftStr: PRawUTF8): RawUTF8; +var i: PtrInt; +begin + for i := length(Str) downto 1 do + if Str[i]=SepChar then begin + result := copy(Str,i+1,maxInt); + if LeftStr<>nil then + LeftStr^ := copy(Str,1,i-1); + exit; + end; + result := Str; + if LeftStr<>nil then + LeftStr^ := ''; +end; + +function SplitRights(const Str, SepChar: RawUTF8): RawUTF8; +var i, j, sep: PtrInt; + c: AnsiChar; +begin + sep := length(SepChar); + if sep > 0 then + if sep = 1 then + result := SplitRight(Str,SepChar[1]) else begin + for i := length(Str) downto 1 do begin + c := Str[i]; + for j := 1 to sep do + if c=SepChar[j] then begin + result := copy(Str,i+1,maxInt); + exit; + end; + end; + end; + result := Str; +end; + +function Split(const Str, SepStr: RawUTF8; StartPos: integer): RawUTF8; +var i: integer; +begin + i := PosEx(SepStr,Str,StartPos); + if i>0 then + result := Copy(Str,StartPos,i-StartPos) else + if StartPos=1 then + result := Str else + result := Copy(Str,StartPos,maxInt); +end; + +procedure Split(const Str, SepStr: RawUTF8; var LeftStr, RightStr: RawUTF8; ToUpperCase: boolean); +var i: integer; + tmp: RawUTF8; // may be called as Split(Str,SepStr,Str,RightStr) +begin + i := PosEx(SepStr,Str); + if i=0 then begin + LeftStr := Str; + RightStr := ''; + end else begin + tmp := copy(Str,1,i-1); + RightStr := copy(Str,i+length(SepStr),maxInt); + LeftStr := tmp; + end; + if ToUpperCase then begin + LeftStr := UpperCaseU(LeftStr); + RightStr := UpperCaseU(RightStr); + end; +end; + +function Split(const Str, SepStr: RawUTF8; var LeftStr: RawUTF8; ToUpperCase: boolean=false): RawUTF8; +begin + Split(Str,SepStr,LeftStr,result,ToUpperCase); +end; + +procedure Split(const Str: RawUTF8; const SepStr: array of RawUTF8; + const DestPtr: array of PRawUTF8); +var s,i,j,n: integer; +begin + j := 1; + n := 0; + s := 0; + if high(SepStr)>=0 then + while n<=high(DestPtr) do begin + i := PosEx(SepStr[s],Str,j); + if i=0 then begin + if DestPtr[n]<>nil then + DestPtr[n]^ := copy(Str,j,MaxInt); + inc(n); + break; + end; + if DestPtr[n]<>nil then + DestPtr[n]^ := copy(Str,j,i-j); + inc(n); + if snil then + DestPtr[i]^ := ''; +end; + +function StringReplaceAll(const S, OldPattern, NewPattern: RawUTF8): RawUTF8; + + procedure Process(found: integer); + var oldlen,newlen,i,last,posCount,sharedlen: integer; + pos: TIntegerDynArray; + src,dst: PAnsiChar; + begin + oldlen := length(OldPattern); + newlen := length(NewPattern); + SetLength(pos,64); + pos[0] := found; + posCount := 1; + repeat + found := PosEx(OldPattern,S,found+oldlen); + if found=0 then + break; + AddInteger(pos,posCount,found); + until false; + FastSetString(result,nil,Length(S)+(newlen-oldlen)*posCount); + last := 1; + src := pointer(s); + dst := pointer(result); + for i := 0 to posCount-1 do begin + sharedlen := pos[i]-last; + {$ifdef FPC}Move{$else}MoveFast{$endif}(src^,dst^,sharedlen); + inc(src,sharedlen+oldlen); + inc(dst,sharedlen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(NewPattern)^,dst^,newlen); + inc(dst,newlen); + last := pos[i]+oldlen; + end; + {$ifdef FPC}Move{$else}MoveFast{$endif}(src^,dst^,length(S)-last+1); + end; + +var j: integer; +begin + if (S='') or (OldPattern='') or (OldPattern=NewPattern) then + result := S else begin + j := PosEx(OldPattern, S, 1); // our PosEx() is faster than Pos() + if j=0 then + result := S else + Process(j); + end; +end; + +function StringReplaceTabs(const Source,TabText: RawUTF8): RawUTF8; + + procedure Process(S,D,T: PAnsiChar; TLen: integer); + begin + repeat + if S^=#0 then + break else + if S^<>#9 then begin + D^ := S^; + inc(D); + inc(S); + end else begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(T^,D^,TLen); + inc(D,TLen); + inc(S); + end; + until false; + end; + +var L,i,n,ttl: PtrInt; +begin + ttl := length(TabText); + L := Length(Source); + n := 0; + if ttl<>0 then + for i := 1 to L do + if Source[i]=#9 then + inc(n); + if n=0 then begin + result := Source; + exit; + end; + SetLength(result,L+n*pred(ttl)); + Process(pointer(Source),pointer(result),pointer(TabText),ttl); +end; + function strspnpas(s,accept: pointer): integer; var p: PCardinal; c: AnsiChar; @@ -26328,8 +25610,9 @@ function strcspnpas(s,reject: pointer): integer; until false; end; +{$ifndef ABSOLUTEPASCAL} {$ifdef CPUINTEL} -{$ifdef CPUX64} +{$ifdef CPUX64} // inspired by Agner Fog's strspn64.asm function strcspnsse42(s,reject: pointer): integer; {$ifdef FPC}nostackframe; assembler; asm {$else} asm // rcx=s, rdx=reject (Linux: rdi,rsi) @@ -26414,7 +25697,7 @@ function strspnsse42(s,accept: pointer): integer; pop rsi pop rdi {$endif}ret -@4: and eax, edx // accumulate matches +@4: or eax, edx // accumulate matches @5: add rsi, 16 // the set is more than 16 bytes movdqu xmm1, [rsi] {$ifdef HASAESNI} @@ -26425,7 +25708,7 @@ function strspnsse42(s,accept: pointer): integer; movd edx, xmm0 jns @4 mov rsi, r8 // restore set pointer - and eax, edx // accumulate matches + or eax, edx // accumulate matches cmp eax, 65535 jne @3 add rdi, 16 // first 16 chars matched, continue with next 16 chars @@ -26520,7 +25803,7 @@ function strspnsse42(s,accept: pointer): integer; pop esi pop edi ret -@4: and eax, edx // accumulate matches +@4: or eax, edx // accumulate matches @5: add esi, 16 // the set is more than 16 bytes {$ifdef HASAESNI} movdqu xmm1, [esi] @@ -26533,7 +25816,7 @@ function strspnsse42(s,accept: pointer): integer; {$endif} jns @4 mov esi, ebx // restore set pointer - and eax, edx // accumulate matches + or eax, edx // accumulate matches cmp eax, 65535 jne @3 add edi, 16 // first 16 chars matched, continue with next 16 chars @@ -26550,6 +25833,7 @@ function StrLenSSE2(S: pointer): PtrInt; pxor xmm0, xmm0 // set to zero and ecx, 15 // lower 4 bits indicate misalignment and eax, -16 // align pointer by 16 + // will never read outside a memory page boundary, so won't trigger GPF movdqa xmm1, [eax] // read from nearest preceding boundary pcmpeqb xmm1, xmm0 // compare 16 bytes with zero pmovmskb edx, xmm1 // get one bit for each byte result @@ -26576,6 +25860,7 @@ function StrLenSSE2(S: pointer): PtrInt; {$endif DELPHI5OROLDER} {$endif CPUX86} {$endif CPUINTEL} +{$endif ABSOLUTEPASCAL} function IdemPropName(const P1,P2: shortstring): boolean; begin @@ -26610,6 +25895,23 @@ function ToText(os: TOperatingSystem): PShortString; result := GetEnumName(TypeInfo(TOperatingSystem),ord(os)); end; +function ToText(const osv: TOperatingSystemVersion): ShortString; +begin + if osv.os=osWindows then + FormatShort('Windows %', [WINDOWS_NAME[osv.win]], result) else + TrimLeftLowerCaseToShort(ToText(osv.os),result); +end; + +function ToTextOS(osint32: integer): RawUTF8; +var osv: TOperatingSystemVersion absolute osint32; + ost: ShortString; +begin + ost := ToText(osv); + if (osv.os>=osLinux) and (osv.utsrelease[2]<>0) then + result := FormatUTF8('% %.%.%',[ost,osv.utsrelease[2],osv.utsrelease[1],osv.utsrelease[0]]) else + result := ShortStringToUTF8(ost); +end; + {$ifdef MSWINDOWS} procedure FileTimeToInt64(const FT: TFileTime; out I64: Int64); begin @@ -26640,17 +25942,20 @@ function GetVersionEx(var lpVersionInformation: TOSVersionInfoEx): BOOL; stdcall external kernel32 name 'GetVersionExA'; {$endif} -function GetSystemTimeMillisecondsForXP: Int64; stdcall; -var fileTime: TFileTime; -begin - GetSystemTimeAsFileTime(fileTime); // very fast, with 100 ns unit - {$ifdef CPU64} // 64 bit XP ? not very likely - but who knows :) - FileTimeToInt64(fileTime,result); - result := result div 10000; - {$else} - result := trunc(PInt64(@fileTime)^/10000); // 100 ns unit - {$endif} -end; +var + GetTickXP: Int64Rec; + +function GetTickCount64ForXP: Int64; stdcall; +var t32: cardinal; + t64: Int64Rec absolute result; +begin // warning: GetSystemTimeAsFileTime() is fast, but not monotonic! + t32 := Windows.GetTickCount; + t64 := GetTickXP; // (almost) atomic read + if t32=wVista then begin - if OSVersionInfo.wProductType<>VER_NT_WORKSTATION then + if OSVersionInfo.wProductType<>VER_NT_WORKSTATION then begin // Server edition inc(Vers,2); // e.g. wEight -> wServer2012 - if SystemInfo.wProcessorArchitecture=PROCESSOR_ARCHITECTURE_AMD64 then + if (Vers=wServer2016) and (OSVersionInfo.dwBuildNumber>=17763) then + Vers := wServer2019_64; // https://stackoverflow.com/q/53393150 + end; + if (SystemInfo.wProcessorArchitecture=PROCESSOR_ARCHITECTURE_AMD64) and + (Vers < wServer2019_64) then inc(Vers); // e.g. wEight -> wEight64 - OpenProcessAccess := PROCESS_QUERY_LIMITED_INFORMATION; - end else - OpenProcessAccess := PROCESS_QUERY_INFORMATION or PROCESS_VM_READ; + end; OSVersion := Vers; with OSVersionInfo do if wServicePackMajor=0 then @@ -26796,45 +26069,11 @@ procedure RetrieveSystemInfo; cpu := GetEnvironmentVariable('PROCESSOR_IDENTIFIER'); cpu := SysUtils.Trim(cpu); FormatUTF8('% x % ('+CPU_ARCH_TEXT+')',[SystemInfo.dwNumberOfProcessors,cpu],CpuInfoText); - @GetSystemTimes := GetProcAddress(Kernel,'GetSystemTimes'); - @GetProcessTimes := GetProcAddress(Kernel,'GetProcessTimes'); - @QueryFullProcessImageNameW := GetProcAddress(Kernel,'QueryFullProcessImageNameW'); - Psapi := LoadLibrary('Psapi.dll'); - if Psapi>=32 then begin - @EnumProcesses := GetProcAddress(Psapi,'EnumProcesses'); - @GetModuleFileNameExW := GetProcAddress(Psapi,'GetModuleFileNameExW'); - @EnumProcessModules := GetProcAddress(Psapi, 'EnumProcessModules'); - @GetProcessMemoryInfo := GetProcAddress(Psapi,'GetProcessMemoryInfo'); - end; end; {$else} -{$ifdef BSD} -function fpsysctlhwint(hwid: cint): Int64; -var mib: array[0..1] of cint; - len: cint; -begin - result := 0; - mib[0] := CTL_HW; - mib[1] := hwid; - len := SizeOf(result); - fpsysctl(pointer(@mib),2,@result,@len,nil,0); -end; -function fpsysctlhwstr(hwid: cint; var temp: shortstring): PUTF8Char; -var mib: array[0..1] of cint; - len: cint; -begin - mib[0] := CTL_HW; - mib[1] := hwid; - FillChar(temp,SizeOf(temp),0); - len := SizeOf(temp); - fpsysctl(pointer(@mib),2,@result,@len,nil,0); - if temp[0]<>#0 then - result := @temp else - result := nil; -end; -{$else} +{$ifndef BSD} procedure SetLinuxDistrib(const release: RawUTF8); var distrib: TOperatingSystem; @@ -26865,7 +26104,7 @@ procedure RetrieveSystemInfo; {$ifdef BSD} fpuname(SystemInfo.uts); SystemInfo.dwNumberOfProcessors := fpsysctlhwint(HW_NCPU); - BiosInfoText := fpsysctlhwstr(HW_MACHINE,temp); + Utf8ToRawUTF8(fpsysctlhwstr(HW_MACHINE,temp),BiosInfoText); modname := fpsysctlhwstr(HW_MODEL,temp); with SystemInfo.uts do FormatUTF8('%-% %',[sysname,release,version],OSVersionText); @@ -26913,7 +26152,7 @@ procedure RetrieveSystemInfo; release := StringToUTF8(SR.Name); release := split(release,'-'); dist := split(trim(StringFromFile('/etc/'+SR.Name)),#10); - if (dist<>'') and (PosEx('=',dist)=0) and (PosEx(' ',dist)>0) then + if (dist<>'') and (PosExChar('=',dist)=0) and (PosExChar(' ',dist)>0) then SetLinuxDistrib(dist) // e.g. 'Red Hat Enterprise Linux Server release 6.7 (Santiago)' else dist := ''; @@ -27023,7 +26262,7 @@ function Elapsed(var PreviousTix: Int64; Interval: Integer): Boolean; begin if Interval<=0 then result := false else begin - now := GetTickCount64; + now := {$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64; if now-PreviousTix>Interval then begin PreviousTix := now; result := true; @@ -27394,7 +26633,7 @@ function SoundExUTF8(U: PUTF8Char; next: PPUTF8Char; {$ifdef USENORMTOUPPER} -function AnsiICompW(u1, u2: PWideChar): PtrInt; +function AnsiICompW(u1, u2: PWideChar): PtrInt; {$ifdef HASINLINE}inline;{$endif} begin if u1<>u2 then if u1<>nil then @@ -27418,17 +26657,20 @@ function AnsiICompW(u1, u2: PWideChar): PtrInt; {$ifdef PUREPASCAL} function AnsiIComp(Str1, Str2: PWinAnsiChar): PtrInt; +var table: PNormTableByte; begin if Str1<>Str2 then if Str1<>nil then - if Str2<>nil then - repeat - result := NormToUpperByte[ord(Str1^)]-NormToUpperByte[pByte(Str2)^]; - if result<>0 then exit; - if (Str1^=#0) or (Str2^=#0) then break; - inc(Str1); - inc(Str2); - until false else + if Str2<>nil then begin + table := @NormToUpperByte; + repeat + result := table[ord(Str1^)]-table[pByte(Str2)^]; + if result<>0 then exit; + if (Str1^=#0) or (Str2^=#0) then break; + inc(Str1); + inc(Str2); + until false; + end else result := 1 else // Str2='' result := -1 else // Str1='' result := 0; // Str1=Str2 @@ -27525,7 +26767,7 @@ function ConvertCaseUTF8(P: PUTF8Char; const Table: TNormTableByte): PtrInt; S := P-1; inc(P,extra); inc(extra); - MoveFast(S^,D[result],extra); + {$ifdef FPC}Move{$else}MoveFast{$endif}(S^,D[result],extra); inc(result,extra); end; until false; @@ -27553,9 +26795,9 @@ function LowerCaseU(const S: RawUTF8): RawUTF8; function UTF8IComp(u1, u2: PUTF8Char): PtrInt; var c2: PtrInt; - table: {$ifdef CPUX86}TNormTableByte absolute NormToUpperByte{$else}PNormTableByte{$endif}; + table: {$ifdef CPUX86NOTPIC}TNormTableByte absolute NormToUpperByte{$else}PNormTableByte{$endif}; begin // fast UTF-8 comparaison using the NormToUpper[] array for all 8 bits values - {$ifndef CPUX86}table := @NormToUpperByte;{$endif} + {$ifndef CPUX86NOTPIC}table := @NormToUpperByte;{$endif} if u1<>u2 then if u1<>nil then if u2<>nil then @@ -27580,7 +26822,7 @@ function UTF8IComp(u1, u2: PUTF8Char): PtrInt; end else begin result := GetHighUTF8UCS4Inlined(u1); if result and $ffffff00=0 then - result := table[result]; // 8 bits to upper, 32 bits as is + result := table[result]; // 8 bits to upper, 32-bit as is end; if c2<=127 then begin if c2=0 then exit; // u1>u2 -> return u1^ @@ -27592,7 +26834,7 @@ function UTF8IComp(u1, u2: PUTF8Char): PtrInt; c2 := GetHighUTF8UCS4Inlined(u2); if c2<=255 then dec(result,table[c2]) else // 8 bits to upper - dec(result,c2); // 32 bits widechar returns diff + dec(result,c2); // 32-bit widechar returns diff if result<>0 then exit; end; until false else @@ -27604,10 +26846,10 @@ function UTF8IComp(u1, u2: PUTF8Char): PtrInt; function UTF8ILComp(u1, u2: PUTF8Char; L1,L2: cardinal): PtrInt; var c2: PtrInt; extra,i: integer; - table: {$ifdef CPUX86}TNormTableByte absolute NormToUpperByte{$else}PNormTableByte{$endif}; + table: {$ifdef CPUX86NOTPIC}TNormTableByte absolute NormToUpperByte{$else}PNormTableByte{$endif}; label neg,pos; begin // fast UTF-8 comparaison using the NormToUpper[] array for all 8 bits values - {$ifndef CPUX86}table := @NormToUpperByte;{$endif} + {$ifndef CPUX86NOTPIC}table := @NormToUpperByte;{$endif} if u1<>u2 then if (u1<>nil) and (L1<>0) then if (u2<>nil) and (L2<>0) then @@ -27642,7 +26884,7 @@ function UTF8ILComp(u1, u2: PUTF8Char; L1,L2: cardinal): PtrInt; dec(result,UTF8_EXTRA[extra].offset); inc(u1,extra); if result and $ffffff00=0 then - result := table[result]; // 8 bits to upper, 32 bits as is + result := table[result]; // 8 bits to upper, 32-bit as is end; // here result=NormToUpper[u1^] inc(u2); @@ -27661,7 +26903,7 @@ function UTF8ILComp(u1, u2: PUTF8Char; L1,L2: cardinal): PtrInt; inc(u2,extra); if c2 and $ffffff00=0 then dec(result,table[c2]) else // 8 bits to upper - dec(result,c2); // returns 32 bits diff + dec(result,c2); // returns 32-bit diff if result<>0 then exit; end; // here we have result=NormToUpper[u2^]-NormToUpper[u1^]=0 @@ -27686,7 +26928,7 @@ function SameTextU(const S1, S2: RawUTF8): Boolean; function AnsiIComp(Str1, Str2: PWinAnsiChar): integer; {$ifdef PUREPASCAL} begin - result := StrIComp(Str1,Str2); // fast enough + result := StrIComp(Str1,Str2); // fast enough, especially since inlined end; {$else} asm @@ -27838,8 +27080,9 @@ function FindUTF8(U: PUTF8Char; UpperValue: PAnsiChar): boolean; end; inc(UpperValue); until false; - // find beginning of next word -Next: +Next: // find beginning of next word + U := FindNextUTF8WordBegin(U); + until U=nil; {$else} // this tiny version only handles 7-bits ansi chars and ignore all UTF-8 chars ValueStart := UpperValue; @@ -27869,21 +27112,21 @@ function FindUTF8(U: PUTF8Char; UpperValue: PAnsiChar): boolean; if byte(U^)=0 then exit else if byte(U^) and $80<>0 then break; // 7 bits char check only until false; -{$endif} // find beginning of next word U := FindNextUTF8WordBegin(U); until U=nil; +{$endif} end; function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: integer): boolean; var B,C: PtrUInt; i: integer; - tab: {$ifdef CPUX86}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; begin result := false; // return false if any invalid char if (Hex=nil) or (Bin=nil) then exit; - {$ifndef CPUX86}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 inc(Bin,BinBytes-1); for i := 1 to BinBytes do begin B := tab[Ord(Hex^)]; @@ -27922,12 +27165,12 @@ function HexDisplayToInt64(const Hex: RawByteString): Int64; function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: Integer): boolean; var I: Integer; B,C: PtrUInt; - tab: {$ifdef CPUX86}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; begin result := false; // return false if any invalid char if Hex=nil then exit; - {$ifndef CPUX86}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 if Bin<>nil then for I := 1 to BinBytes do begin B := tab[Ord(Hex^)]; @@ -28224,13 +27467,13 @@ function BinToBase64(const data, Prefix, Suffix: RawByteString; WithMagic: boole if WithMagic then inc(len,3); SetLength(result,len); - MoveFast(pointer(Prefix)^,res[0],lenprefix); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Prefix)^,res[0],lenprefix); if WithMagic then begin PInteger(@res[lenprefix])^ := JSON_BASE64_MAGIC; inc(lenprefix,3); end; Base64Encode(@res[lenprefix],pointer(data),lendata); - MoveFast(pointer(Suffix)^,res[len-lensuffix],lensuffix); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Suffix)^,res[len-lensuffix],lensuffix); end; function BinToBase64WithMagic(const data: RawByteString): RawUTF8; @@ -28563,6 +27806,12 @@ procedure Base64uriToBin(sp: PAnsiChar; len: PtrInt; var result: RawByteString); result := ''; end; +function Base64uriToBin(sp: PAnsiChar; len: PtrInt; var temp: TSynTempBuffer): boolean; +begin + temp.Init(Base64uriToBinLength(len)); + result := (temp.len>0) and Base64AnyDecode(ConvertBase64URIToBin,sp,temp.buf,len); +end; + function Base64uriToBin(const base64: RawByteString; bin: PAnsiChar; binlen: PtrInt): boolean; begin result := Base64uriToBin(pointer(base64),bin,length(base64),binlen); @@ -28680,6 +27929,24 @@ function LowerCaseUnicode(const S: RawUTF8): RawUTF8; {$endif} end; +function IsCaseSensitive(const S: RawUTF8): boolean; +begin + result := IsCaseSensitive(pointer(S),length(S)); +end; + +function IsCaseSensitive(P: PUTF8Char; PLen: integer): boolean; +begin + result := true; + if (P<>nil) and (PLen>0) then + repeat + if ord(P^) in [ord('a')..ord('z'), ord('A')..ord('Z')] then + exit; + inc(P); + dec(PLen); + until PLen=0; + result := false; +end; + function UpperCase(const S: RawUTF8): RawUTF8; var L, i: PtrInt; begin @@ -28783,15 +28050,16 @@ function TrimRight(const S: RawUTF8): RawUTF8; TwoDigitsHexWBLower: array[byte] of word absolute TwoDigitsHexLower; procedure BinToHex(Bin, Hex: PAnsiChar; BinBytes: integer); -var j: cardinal; - {$ifdef PUREPASCAL}tab: ^TAnsiCharToWord;{$endif} +{$ifdef PUREPASCAL}var tab: ^TAnsiCharToWord;{$endif} begin {$ifdef PUREPASCAL}tab := @TwoDigitsHexW;{$endif} - for j := 1 to BinBytes do begin - PWord(Hex)^ := {$ifndef PUREPASCAL}TwoDigitsHexW{$else}tab{$endif}[Bin^]; - inc(Hex,2); - inc(Bin); - end; + if BinBytes>0 then + repeat + PWord(Hex)^ := {$ifndef PUREPASCAL}TwoDigitsHexW{$else}tab{$endif}[Bin^]; + inc(Bin); + inc(Hex,2); + dec(BinBytes); + until BinBytes=0; end; function BinToHex(const Bin: RawByteString): RawUTF8; @@ -28862,6 +28130,20 @@ function LogEscape(source: PAnsiChar; sourcelen: integer; var temp: TLogEscape; result := @temp; end; +function LogEscapeFull(const source: RawByteString): RawUTF8; +begin + result := LogEscapeFull(pointer(source),length(source)); +end; + +function LogEscapeFull(source: PAnsiChar; sourcelen: integer): RawUTF8; +begin + SetLength(result,sourcelen*3); // worse case + if sourcelen=0 then + exit; + sourcelen := EscapeBuffer(source,pointer(result),sourcelen,length(result))-pointer(result); + SetLength(result,sourcelen); +end; + function EscapeToShort(source: PAnsiChar; sourcelen: integer): shortstring; begin result[0] := AnsiChar(EscapeBuffer(source,@result[1],sourcelen,80)-@result[1]); @@ -28873,14 +28155,17 @@ function EscapeToShort(const source: RawByteString): shortstring; overload; end; procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: integer); -var j: integer; - {$ifdef PUREPASCAL}tab: ^TAnsiCharToWord;{$endif} +{$ifdef PUREPASCAL}var tab: ^TAnsiCharToWord;{$endif} begin {$ifdef PUREPASCAL}tab := @TwoDigitsHexW;{$endif} - for j := BinBytes-1 downto 0 do begin - PWord(Hex+j*2)^ := {$ifndef PUREPASCAL}TwoDigitsHexW{$else}tab{$endif}[Bin^]; - inc(Bin); - end; + inc(Hex,BinBytes*2); + if BinBytes>0 then + repeat + dec(Hex,2); + PWord(Hex)^ := {$ifndef PUREPASCAL}TwoDigitsHexW{$else}tab{$endif}[Bin^]; + inc(Bin); + dec(BinBytes); + until BinBytes=0; end; function BinToHexDisplay(Bin: PAnsiChar; BinBytes: integer): RawUTF8; @@ -28890,15 +28175,16 @@ function BinToHexDisplay(Bin: PAnsiChar; BinBytes: integer): RawUTF8; end; procedure BinToHexLower(Bin, Hex: PAnsiChar; BinBytes: integer); -var j: cardinal; - {$ifdef PUREPASCAL}tab: ^TAnsiCharToWord;{$endif} +{$ifdef PUREPASCAL}var tab: ^TAnsiCharToWord;{$endif} begin {$ifdef PUREPASCAL}tab := @TwoDigitsHexWLower;{$endif} - for j := 1 to BinBytes do begin - PWord(Hex)^ := {$ifndef PUREPASCAL}TwoDigitsHexWLower{$else}tab{$endif}[Bin^]; - inc(Hex,2); - inc(Bin); - end; + if BinBytes>0 then + repeat + PWord(Hex)^ := {$ifndef PUREPASCAL}TwoDigitsHexWLower{$else}tab{$endif}[Bin^]; + inc(Bin); + inc(Hex,2); + dec(BinBytes); + until BinBytes=0; end; function BinToHexLower(const Bin: RawByteString): RawUTF8; @@ -28917,15 +28203,18 @@ function BinToHexLower(Bin: PAnsiChar; BinBytes: integer): RawUTF8; BinToHexLower(Bin,BinBytes,result); end; -procedure BinToHexDisplayLower(Bin, Hex: PAnsiChar; BinBytes: integer); -var j: integer; - {$ifdef PUREPASCAL}tab: ^TAnsiCharToWord;{$endif} +procedure BinToHexDisplayLower(Bin, Hex: PAnsiChar; BinBytes: PtrInt); +{$ifdef PUREPASCAL}var tab: ^TAnsiCharToWord;{$endif} begin {$ifdef PUREPASCAL}tab := @TwoDigitsHexWLower;{$endif} - for j := BinBytes-1 downto 0 do begin - PWord(Hex+j*2)^ := {$ifndef PUREPASCAL}TwoDigitsHexWLower{$else}tab{$endif}[Bin^]; - inc(Bin); - end; + inc(Hex,BinBytes*2); + if BinBytes>0 then + repeat + dec(Hex,2); + PWord(Hex)^ := {$ifndef PUREPASCAL}TwoDigitsHexWLower{$else}tab{$endif}[Bin^]; + inc(Bin); + dec(BinBytes); + until BinBytes=0; end; function BinToHexDisplayLower(Bin: PAnsiChar; BinBytes: integer): RawUTF8; @@ -29104,20 +28393,18 @@ function UInt2DigitsToShortFast(Value: byte): TShort4; PWord(@result[1])^ := TwoDigitLookupW[Value]; end; -const - DOUBLE_RESOLUTION = 1E-12; // also for TSynExtended (FPC uses 1E-4!) - function SameValue(const A, B: Double; DoublePrec: double): Boolean; -var AbsA,AbsB: double; +var AbsA,AbsB,Res: double; begin if PInt64(@DoublePrec)^=0 then begin // Max(Min(Abs(A),Abs(B))*1E-12,1E-12) AbsA := Abs(A); AbsB := Abs(B); + Res := 1E-12; if AbsAB then + result := 1 else + result := 0; +end; + +function CompareInteger(const A, B: integer): integer; +begin + if AB then + result := 1 else + result := 0; +end; + +function CompareInt64(const A, B: Int64): integer; +begin + if AB then + result := 1 else + result := 0; +end; + +function CompareCardinal(const A, B: cardinal): integer; +begin + if AB then + result := 1 else + result := 0; +end; + procedure KahanSum(const Data: double; var Sum, Carry: double); var y, t: double; begin @@ -29229,15 +28553,27 @@ function AddRawUTF8(var Values: TRawUTF8DynArray; const Value: RawUTF8; result := true; end; +function NextGrow(capacity: integer): integer; +begin // algorithm similar to TFPList.Expand for the increasing ranges + result := capacity; + if result<128 shl 20 then + if result<8 shl 20 then + if result<=128 then + if result>8 then + inc(result,16) else + inc(result,4) else + inc(result,result shr 2) else + inc(result,result shr 3) else + inc(result,16 shl 20); +end; + procedure AddRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer; const Value: RawUTF8); var capacity: integer; begin capacity := Length(Values); - if ValuesCount=capacity then begin - inc(capacity,32+capacity shr 3); - SetLength(Values,capacity); - end; + if ValuesCount=capacity then + SetLength(Values,NextGrow(capacity)); Values[ValuesCount] := Value; inc(ValuesCount); end; @@ -29281,8 +28617,6 @@ procedure StringListToRawUTF8DynArray(Source: TStringList; var Result: TRawUTF8D StringToUTF8(Source[i],Result[i]); end; -/// find the position of the SEARCH] section in source -// - return true if SEARCH] was found, and store line after it in source function FindSectionFirstLine(var source: PUTF8Char; search: PAnsiChar): boolean; {$ifdef PUREPASCAL} begin @@ -29353,7 +28687,6 @@ function FindSectionFirstLine(var source: PUTF8Char; search: PAnsiChar): boolean {$ifdef USENORMTOUPPER} {$ifdef PUREPASCAL} function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean; -// if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper) begin result := false; if (p=nil) or (up=nil) then @@ -29368,9 +28701,7 @@ function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean; end; {$else} function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean; -// if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper) -// eax=p edx=up -asm +asm // eax=p edx=up test eax, eax jz @e // P=nil -> false test edx, edx @@ -29399,7 +28730,7 @@ function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean; pop esi pop ebx end; -{$endif} +{$endif PUREPASCAL} {$else} function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean; // if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper) @@ -29415,7 +28746,7 @@ function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean; end; result := true; end; -{$endif} +{$endif USENORMTOUPPER} function FindSectionFirstLineW(var source: PWideChar; search: PUTF8Char): boolean; {$ifdef PUREPASCAL} @@ -29480,15 +28811,15 @@ function FindSectionFirstLineW(var source: PWideChar; search: PUTF8Char): boolea ret @z: pop edx // ignore source var, result := false end; -{$endif} +{$endif PUREPASCAL} function FindIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8; var u, PBeg: PUTF8Char; by4: cardinal; - table: {$ifdef CPUX86}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; + table: {$ifdef CPUX86NOTPIC}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; begin // expect UpperName as 'NAME=' if (P<>nil) and (P^<>'[') and (UpperName<>nil) then begin - {$ifndef CPUX86}table := @NormToUpperAnsi7;{$endif} + {$ifndef CPUX86NOTPIC}table := @NormToUpperAnsi7;{$endif} PBeg := nil; u := P; repeat @@ -29775,7 +29106,6 @@ procedure UpdateIniEntry(var Content: RawUTF8; const Section,Name,Value: RawUTF8 i, UpperNameLength: PtrInt; V: RawUTF8; UpperSection, UpperName: array[byte] of AnsiChar; - // possible GPF if length(Section/Name)>255, but should be short const in code label Sec; begin UpperNameLength := length(Name); @@ -29834,7 +29164,7 @@ function StringFromFile(const FileName: TFileName; HasNoSize: boolean): RawByteS if Read<=0 then break; SetLength(result,Size+Read); - MoveFast(tmp,PByteArray(result)^[Size],Read); + {$ifdef FPC}Move{$else}MoveFast{$endif}(tmp,PByteArray(result)^[Size],Read); inc(Size,Read); until false; end else begin @@ -30104,35 +29434,36 @@ function FileInfoByHandle(aFileHandle: THandle; out FileId, FileSize, begin {$ifdef MSWINDOWS} result := GetFileInformationByHandle(aFileHandle,lp); - if result then begin - LastWriteAccess := FileTimeToUnixMSTime(lp.ftLastWriteTime); - FileCreateDateTime := FileTimeToUnixMSTime(lp.ftCreationTime); - lastreadaccess := FileTimeToUnixMSTime(lp.ftLastAccessTime); - PInt64Rec(@FileSize).lo := lp.nFileSizeLow; - PInt64Rec(@FileSize).hi := lp.nFileSizeHigh; - PInt64Rec(@FileId).lo := lp.nFileIndexLow; - PInt64Rec(@FileId).hi := lp.nFileIndexHigh; + if not result then + exit; + LastWriteAccess := FileTimeToUnixMSTime(lp.ftLastWriteTime); + FileCreateDateTime := FileTimeToUnixMSTime(lp.ftCreationTime); + lastreadaccess := FileTimeToUnixMSTime(lp.ftLastAccessTime); + PInt64Rec(@FileSize).lo := lp.nFileSizeLow; + PInt64Rec(@FileSize).hi := lp.nFileSizeHigh; + PInt64Rec(@FileId).lo := lp.nFileIndexLow; + PInt64Rec(@FileId).hi := lp.nFileIndexHigh; {$else} - r := {$ifdef FPC}FpFStat{$else}fstat64{$endif}(aFileHandle, lp); + r := {$ifdef FPC}FpFStat{$else}fstat64{$endif}(aFileHandle, lp); result := r >= 0; - if result then begin - FileId := lp.st_ino; - FileSize := lp.st_size; - lastreadaccess := lp.st_atime * MSecsPerSec; - LastWriteAccess := lp.st_mtime * MSecsPerSec; - {$ifdef OPENBSD} - if (lp.st_birthtime <> 0) and (lp.st_birthtime < lp.st_ctime) then - lp.st_ctime:= lp.st_birthtime; - {$endif} - FileCreateDateTime := lp.st_ctime * MSecsPerSec; + if not result then + exit; + FileId := lp.st_ino; + FileSize := lp.st_size; + lastreadaccess := lp.st_atime * MSecsPerSec; + LastWriteAccess := lp.st_mtime * MSecsPerSec; + {$ifdef OPENBSD} + if (lp.st_birthtime <> 0) and (lp.st_birthtime < lp.st_ctime) then + lp.st_ctime:= lp.st_birthtime; + {$endif} + FileCreateDateTime := lp.st_ctime * MSecsPerSec; {$endif MSWINDOWS} - if LastWriteAccess <> 0 then - if (FileCreateDateTime = 0) or (FileCreateDateTime > LastWriteAccess) then - FileCreateDateTime:= LastWriteAccess; - if lastreadaccess <> 0 then - if (FileCreateDateTime = 0) or (FileCreateDateTime > lastreadaccess) then - FileCreateDateTime:= lastreadaccess; - end; + if LastWriteAccess <> 0 then + if (FileCreateDateTime = 0) or (FileCreateDateTime > LastWriteAccess) then + FileCreateDateTime:= LastWriteAccess; + if lastreadaccess <> 0 then + if (FileCreateDateTime = 0) or (FileCreateDateTime > lastreadaccess) then + FileCreateDateTime:= lastreadaccess; end; function FileAgeToDateTime(const FileName: TFileName): TDateTime; @@ -30276,7 +29607,7 @@ procedure TFindFiles.FromSearchRec(const Directory: TFileName; const F: TSearchR {$ifdef MSWINDOWS} {$ifdef HASINLINE} // FPC or Delphi 2006+ Size := F.Size; - {$else} // F.Size was limited to 32 bits on older Delphi + {$else} // F.Size was limited to 32-bit on older Delphi Size := F.FindData.nFileSizeLow or Int64(F.FindData.nFileSizeHigh) shl 32; {$endif} {$else} @@ -30322,7 +29653,7 @@ function FindFiles(const Directory,Mask,IgnoreFileName: TFileName; if SearchRecValidFile(F) and ((IgnoreFileName='') or (AnsiCompareFileName(F.Name,IgnoreFileName)<>0)) then begin if n=length(result) then - SetLength(result,n+n shr 3+8); + SetLength(result,NextGrow(n)); if IncludesDir then result[n].FromSearchRec(Dir,F) else result[n].FromSearchRec('',F); @@ -30356,7 +29687,7 @@ function EnsureDirectoryExists(const Directory: TFileName; if not CreateDir(result) then if not RaiseExceptionOnCreationFailure then result := '' else - raise ESynException.CreateUTF8('Impossible to create "%" folder',[Directory]); + raise ESynException.CreateUTF8('Impossible to create folder %',[result]); end; var @@ -30370,10 +29701,23 @@ function TemporaryFileName: TFileName; TemporaryFileNameRandom := Random32; repeat // thread-safe unique file name generation FormatString('%%_%.tmp',[folder,ExeVersion.ProgramName, - CardinalToHexShort(InterlockedIncrement(TemporaryFileNameRandom))], string(result)); + CardinalToHexShort(InterlockedIncrement(TemporaryFileNameRandom))],string(result)); until not FileExists(result); end; +function IsDirectoryWritable(const Directory: TFileName): boolean; +var fn: TFileName; +begin + fn := ExcludeTrailingPathDelimiter(Directory); + result := {$ifndef DELPHI5OROLDER}not FileIsReadOnly{$else}DirectoryExists{$endif}(fn); + if not result then + exit; + fn := FormatString('%%.%%',[fn,PathDelim,CardinalToHexShort(integer(GetCurrentThreadID)), + BinToBase64uriShort(@ExeVersion.Hash,SizeOf(ExeVersion.Hash))]); + result := FileFromString('tobedeleted',fn); // actually try to write something + DeleteFile(fn); +end; + {$ifdef DELPHI5OROLDER} function DirectoryExists(const Directory: string): boolean; @@ -30585,7 +29929,7 @@ procedure AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; Value: integer); begin if ValuesCount=length(Values) then - SetLength(Values,ValuesCount+256+ValuesCount shr 3); + SetLength(Values,NextGrow(ValuesCount)); Values[ValuesCount] := Value; inc(ValuesCount); end; @@ -30598,37 +29942,61 @@ function AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer; exit; end; if ValuesCount=length(Values) then - SetLength(Values,ValuesCount+256+ValuesCount shr 3); + SetLength(Values,NextGrow(ValuesCount)); Values[ValuesCount] := Value; inc(ValuesCount); - result := true + result := true; +end; + +function AddInteger(var Values: TIntegerDynArray; const Another: TIntegerDynArray): PtrInt; +var v,a: PtrInt; +begin + v := length(Values); + a := length(Another); + if a>0 then begin + SetLength(Values,v+a); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Another[0],Values[v],a*SizeOf(Integer)); + end; + result := v+a; end; -function AddWord(var Values: TWordDynArray; var ValuesCount: integer; Value: Word): integer; +function AddWord(var Values: TWordDynArray; var ValuesCount: integer; Value: Word): PtrInt; begin result := ValuesCount; if result=length(Values) then - SetLength(Values,result+32+result shr 3); + SetLength(Values,NextGrow(result)); Values[result] := Value; inc(ValuesCount); end; -function AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64): integer; +function AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64): PtrInt; begin result := ValuesCount; if result=length(Values) then - SetLength(Values,result+256+result shr 3); + SetLength(Values,NextGrow(result)); Values[result] := Value; inc(ValuesCount); end; -function AddInt64(var Values: TInt64DynArray; Value: Int64): integer; +function AddInt64(var Values: TInt64DynArray; Value: Int64): PtrInt; begin result := length(Values); SetLength(Values,result+1); Values[result] := Value; end; +function AddInt64(var Values: TInt64DynArray; const Another: TInt64DynArray): PtrInt; +var v,a: PtrInt; +begin + v := length(Values); + a := length(Another); + if a>0 then begin + SetLength(Values,v+a); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Another[0],Values[v],a*SizeOf(Int64)); + end; + result := v+a; +end; + procedure AddInt64Sorted(var Values: TInt64DynArray; Value: Int64); var last: integer; begin @@ -30641,7 +30009,7 @@ procedure AddInt64Sorted(var Values: TInt64DynArray; Value: Int64); end; end; -function AddInt64Once(var Values: TInt64DynArray; Value: Int64): integer; +function AddInt64Once(var Values: TInt64DynArray; Value: Int64): PtrInt; begin result := Int64ScanIndex(pointer(Values),length(Values),Value); if result<0 then @@ -30656,7 +30024,7 @@ procedure DeleteWord(var Values: TWordDynArray; Index: PtrInt); exit; // wrong Index dec(n); if n>Index then - MoveFast(Values[Index+1],Values[Index],(n-Index)*SizeOf(Word)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values[Index+1],Values[Index],(n-Index)*SizeOf(Word)); SetLength(Values,n); end; @@ -30668,7 +30036,7 @@ procedure DeleteInteger(var Values: TIntegerDynArray; Index: PtrInt); exit; // wrong Index dec(n); if n>Index then - MoveFast(Values[Index+1],Values[Index],(n-Index)*SizeOf(Integer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values[Index+1],Values[Index],(n-Index)*SizeOf(Integer)); SetLength(Values,n); end; @@ -30680,7 +30048,7 @@ procedure DeleteInteger(var Values: TIntegerDynArray; var ValuesCount: Integer; exit; // wrong Index dec(n,Index+1); if n>0 then - MoveFast(Values[Index+1],Values[Index],n*SizeOf(Integer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values[Index+1],Values[Index],n*SizeOf(Integer)); dec(ValuesCount); end; @@ -30692,7 +30060,7 @@ procedure DeleteInt64(var Values: TInt64DynArray; Index: PtrInt); exit; // wrong Index dec(n); if n>Index then - MoveFast(Values[Index+1],Values[Index],(n-Index)*SizeOf(Int64)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values[Index+1],Values[Index],(n-Index)*SizeOf(Int64)); SetLength(Values,n); end; @@ -30704,12 +30072,12 @@ procedure DeleteInt64(var Values: TInt64DynArray; var ValuesCount: Integer; Inde exit; // wrong Index dec(n,Index+1); if n>0 then - MoveFast(Values[Index+1],Values[Index],n*SizeOf(Int64)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values[Index+1],Values[Index],n*SizeOf(Int64)); dec(ValuesCount); end; procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray; ExcludedSortSize: integer); -var i,v,x,n: integer; +var i,v,x,n: PtrInt; begin if (Values=nil) or (Excluded=nil) then exit; // nothing to exclude @@ -30736,8 +30104,39 @@ procedure ExcludeInteger(var Values, Excluded: TIntegerDynArray; ExcludedSortSiz SetLength(Values,n); end; +procedure IncludeInteger(var Values, Included: TIntegerDynArray; + IncludedSortSize: Integer); +var i,v,x,n: PtrInt; +begin + if (Values=nil) or (Included=nil) then begin + Values := nil; + exit; + end; + v := length(Values); + n := 0; + x := Length(Included); + if (x>IncludedSortSize) or (v>IncludedSortSize) then begin // sort if worth it + dec(x); + QuickSortInteger(pointer(Included),0,x); + for i := 0 to v-1 do + if FastFindIntegerSorted(pointer(Included),x,Values[i])>=0 then begin + if n<>i then + Values[n] := Values[i]; + inc(n); + end; + end else + for i := 0 to v-1 do + if IntegerScanExists(pointer(Included),x,Values[i]) then begin + if n<>i then + Values[n] := Values[i]; + inc(n); + end; + if n<>v then + SetLength(Values,n); +end; + procedure ExcludeInt64(var Values, Excluded: TInt64DynArray; ExcludedSortSize: Integer); -var i,v,x,n: integer; +var i,v,x,n: PtrInt; begin if (Values=nil) or (Excluded=nil) then exit; // nothing to exclude @@ -30764,6 +30163,37 @@ procedure ExcludeInt64(var Values, Excluded: TInt64DynArray; ExcludedSortSize: I SetLength(Values,n); end; +procedure IncludeInt64(var Values, Included: TInt64DynArray; + IncludedSortSize: integer); +var i,v,x,n: PtrInt; +begin + if (Values=nil) or (Included=nil) then begin + Values := nil; + exit; + end; + v := length(Values); + n := 0; + x := Length(Included); + if (x>IncludedSortSize) or (v>IncludedSortSize) then begin // sort if worth it + dec(x); + QuickSortInt64(pointer(Included),0,x); + for i := 0 to v-1 do + if FastFindInt64Sorted(pointer(Included),x,Values[i])>=0 then begin + if n<>i then + Values[n] := Values[i]; + inc(n); + end; + end else + for i := 0 to v-1 do + if Int64ScanExists(pointer(Included),x,Values[i]) then begin + if n<>i then + Values[n] := Values[i]; + inc(n); + end; + if n<>v then + SetLength(Values,n); +end; + procedure DeduplicateInteger(var Values: TIntegerDynArray); begin DeduplicateInteger(Values, length(Values)); @@ -30857,7 +30287,7 @@ procedure CopyInteger(const Source: TIntegerDynArray; out Dest: TIntegerDynArray begin n := length(Source); SetLength(Dest,n); - MoveFast(Source[0],Dest[0],n*SizeOf(Integer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source[0],Dest[0],n*SizeOf(Integer)); end; procedure CopyInt64(const Source: TInt64DynArray; out Dest: TInt64DynArray); @@ -30865,7 +30295,7 @@ procedure CopyInt64(const Source: TInt64DynArray; out Dest: TInt64DynArray); begin n := length(Source); SetLength(Dest,n); - MoveFast(Source[0],Dest[0],n*SizeOf(Int64)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source[0],Dest[0],n*SizeOf(Int64)); end; function MaxInteger(const Values: TIntegerDynArray; ValuesCount, MaxStart: integer): Integer; @@ -30987,7 +30417,7 @@ function IntegerDynArrayToCSV(Values: PIntegerArray; ValuesCount: integer; SetLength(result,Len); P := pointer(result); if Prefix<>'' then begin - MoveFast(pointer(Prefix)^,P^,length(Prefix)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Prefix)^,P^,length(Prefix)); inc(P,length(Prefix)); end; for i := 0 to ValuesCount do begin @@ -30995,7 +30425,7 @@ function IntegerDynArrayToCSV(Values: PIntegerArray; ValuesCount: integer; PWord(P)^ := ord(':')+ord('(')shl 8; inc(P,2); end; - MoveFast(ints[i][1],P^,ord(ints[i][0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}(ints[i][1],P^,ord(ints[i][0])); inc(P,ord(ints[i][0])); if InlinedValue then begin PWord(P)^ := ord(')')+ord(':')shl 8; @@ -31003,7 +30433,7 @@ function IntegerDynArrayToCSV(Values: PIntegerArray; ValuesCount: integer; end; end; if Suffix<>'' then - MoveFast(pointer(Suffix)^,P^,length(Suffix)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Suffix)^,P^,length(Suffix)); finally tmpbuf.Done; end; @@ -31045,7 +30475,7 @@ function Int64DynArrayToCSV(Values: PInt64Array; ValuesCount: integer; SetLength(result,Len); P := pointer(result); if Prefix<>'' then begin - MoveFast(pointer(Prefix)^,P^,length(Prefix)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Prefix)^,P^,length(Prefix)); inc(P,length(Prefix)); end; int := tmp.buf; @@ -31055,7 +30485,7 @@ function Int64DynArrayToCSV(Values: PInt64Array; ValuesCount: integer; inc(P,2); end; L := int^.Len; - MoveFast(PAnsiChar(int)[21-L],P^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(PAnsiChar(int)[21-L],P^,L); inc(P,L); if InlinedValue then begin PWord(P)^ := ord(')')+ord(':')shl 8; @@ -31069,7 +30499,7 @@ function Int64DynArrayToCSV(Values: PInt64Array; ValuesCount: integer; dec(ValuesCount); until false; if Suffix<>'' then - MoveFast(pointer(Suffix)^,P^,length(Suffix)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Suffix)^,P^,length(Suffix)); finally tmp.Done; end; @@ -31164,7 +30594,7 @@ function PtrUIntScanIndex(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): PtrI function WordScanIndex(P: PWordArray; Count: PtrInt; Value: word): integer; begin {$ifdef FPC} - result := IndexWord(P^,Count,Value); + result := IndexWord(P^,Count,Value); // will use fast FPC SSE version {$else} for result := 0 to Count-1 do if P^[result]=Value then @@ -31191,10 +30621,16 @@ procedure QuickSortInteger(ID: PIntegerArray; L,R: PtrInt); inc(I); dec(J); end; until I > J; - if L < J then - QuickSortInteger(ID,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortInteger(ID, L, J); + L := I; + end else begin + if I < R then + QuickSortInteger(ID, I, R); + R := J; + end; + until L >= R; end; procedure QuickSortInteger(var ID: TIntegerDynArray); @@ -31221,10 +30657,16 @@ procedure QuickSortInteger(ID,CoValues: PIntegerArray; L,R: PtrInt); inc(I); dec(J); end; until I > J; - if L < J then - QuickSortInteger(ID,CoValues,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortInteger(ID, CoValues, L, J); + L := I; + end else begin + if I < R then + QuickSortInteger(ID, CoValues, I, R); + R := J; + end; + until L >= R; end; procedure QuickSortWord(ID: PWordArray; L, R: PtrInt); @@ -31245,10 +30687,16 @@ procedure QuickSortWord(ID: PWordArray; L, R: PtrInt); inc(I); dec(J); end; until I > J; - if L < J then - QuickSortWord(ID,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortWord(ID, L, J); + L := I; + end else begin + if I < R then + QuickSortWord(ID, I, R); + R := J; + end; + until L >= R; end; procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); @@ -31274,10 +30722,16 @@ procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); inc(I); dec(J); end; until I > J; - if L < J then - QuickSortInt64(ID,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortInt64(ID, L, J); + L := I; + end else begin + if I < R then + QuickSortInt64(ID, I, R); + R := J; + end; + until L >= R; end; procedure QuickSortQWord(ID: PQWordArray; L, R: PtrInt); @@ -31303,10 +30757,16 @@ procedure QuickSortQWord(ID: PQWordArray; L, R: PtrInt); inc(I); dec(J); end; until I > J; - if L < J then - QuickSortQWord(ID,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortQWord(ID, L, J); + L := I; + end else begin + if I < R then + QuickSortQWord(ID, I, R); + R := J; + end; + until L >= R; end; procedure QuickSortInt64(ID,CoValues: PInt64Array; L, R: PtrInt); @@ -31333,10 +30793,16 @@ procedure QuickSortInt64(ID,CoValues: PInt64Array; L, R: PtrInt); inc(I); dec(J); end; until I > J; - if L < J then - QuickSortInt64(ID,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortInt64(ID, CoValues, L, J); + L := I; + end else begin + if I < R then + QuickSortInt64(ID, CoValues, I, R); + R := J; + end; + until L >= R; end; procedure QuickSortPtrInt(P: PPtrIntArray; L, R: PtrInt); @@ -31404,7 +30870,7 @@ procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer; begin if ValuesCount>length(Dest) then SetLength(Dest,ValuesCount); - MoveFast(Values^[0],Dest[0],ValuesCount*SizeOf(Integer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values^[0],Dest[0],ValuesCount*SizeOf(Integer)); QuickSortInteger(pointer(Dest),0,ValuesCount-1); end; @@ -31413,7 +30879,7 @@ procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer; begin if ValuesCount>length(Dest) then SetLength(Dest,ValuesCount); - MoveFast(Values^[0],Dest[0],ValuesCount*SizeOf(Int64)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Values^[0],Dest[0],ValuesCount*SizeOf(Int64)); QuickSortInt64(pointer(Dest),0,ValuesCount-1); end; @@ -31457,21 +30923,26 @@ function FastFindInt64Sorted(P: PInt64Array; R: PtrInt; const Value: Int64): Ptr if 0<=R then repeat result := (L + R) shr 1; - {$ifdef CPUX86} // circumvent Int64 comparison slowness + {$ifndef CPUX86} + if P^[result]=Value then + exit else + if P^[result]nil then SetLength(CoValues^,n); @@ -31570,9 +31046,9 @@ function InsertInteger(var Values: TIntegerDynArray; var ValuesCount: integer; n := ValuesCount; if PtrUInt(result)nil then - MoveFast(CoValues^[result],CoValues^[result+1],n); + {$ifdef FPC}Move{$else}MoveFast{$endif}(CoValues^[result],CoValues^[result+1],n); end else result := n; Values[result] := Value; @@ -31924,16 +31400,16 @@ procedure SetInt64(P: PUTF8Char; var result: Int64); c := byte(P^)-48; if c>9 then exit; - Int64Rec(result).Lo := c; + PCardinal(@result)^ := c; inc(P); - repeat // fast 32 bit loop + repeat // fast 32-bit loop c := byte(P^)-48; if c>9 then break else - Int64Rec(result).Lo := Int64Rec(result).Lo*10+c; + PCardinal(@result)^ := PCardinal(@result)^*10+c; inc(P); - if Int64Rec(result).Lo>=high(cardinal)div 10 then begin - repeat // 64 bit loop + if PCardinal(@result)^>=high(cardinal)div 10 then begin + repeat // 64-bit loop c := byte(P^)-48; if c>9 then break; @@ -31967,16 +31443,16 @@ procedure SetQWord(P: PUTF8Char; var result: QWord); c := byte(P^)-48; if c>9 then exit; - Int64Rec(result).Lo := c; + PCardinal(@result)^ := c; inc(P); - repeat // fast 32 bit loop + repeat // fast 32-bit loop c := byte(P^)-48; if c>9 then break else - Int64Rec(result).Lo := Int64Rec(result).Lo*10+c; + PCardinal(@result)^ := PCardinal(@result)^*10+c; inc(P); - if Int64Rec(result).Lo>=high(cardinal)div 10 then begin - repeat // 64 bit loop + if PCardinal(@result)^>=high(cardinal)div 10 then begin + repeat // 64-bit loop c := byte(P^)-48; if c>9 then break; @@ -32037,19 +31513,19 @@ function GetInt64(P: PUTF8Char; var err: integer): Int64; c := byte(P^)-48; if c>9 then exit; - Int64Rec(result).Lo := c; + PCardinal(@result)^ := c; inc(P); - repeat // fast 32 bit loop + repeat // fast 32-bit loop c := byte(P^); if c<>0 then begin dec(c,48); inc(err); if c>9 then exit; - Int64Rec(result).Lo := Int64Rec(result).Lo*10+c; + PCardinal(@result)^ := PCardinal(@result)^*10+c; inc(P); - if Int64Rec(result).Lo>=high(cardinal)div 10 then begin - repeat // 64 bit loop + if PCardinal(@result)^>=high(cardinal)div 10 then begin + repeat // 64-bit loop c := byte(P^); if c=0 then begin err := 0; // conversion success without error @@ -32082,30 +31558,44 @@ function GetInt64(P: PUTF8Char; var err: integer): Int64; {$endif} function GetQWord(P: PUTF8Char; var err: integer): QWord; -var c: cardinal; +var c: PtrUInt; begin - err := 0; + err := 1; // error result := 0; if P=nil then exit; while (P^<=' ') and (P^<>#0) do inc(P); - inc(err); c := byte(P^)-48; if c>9 then exit; - Int64Rec(result).Lo := c; + {$ifdef CPU64} + result := c; inc(P); - repeat // fast 32 bit loop + repeat + c := byte(P^); + if c=0 then + break; + dec(c,48); + if c>9 then + exit; + result := result*10+c; + inc(P); + until false; + err := 0; // success + {$else} + PByte(@result)^ := c; + inc(P); + repeat // fast 32-bit loop c := byte(P^); if c<>0 then begin dec(c,48); inc(err); if c>9 then exit; - Int64Rec(result).Lo := Int64Rec(result).Lo*10+c; + PCardinal(@result)^ := PCardinal(@result)^*10+c; inc(P); - if Int64Rec(result).Lo>=high(cardinal)div 10 then begin - repeat // 64 bit loop + if PCardinal(@result)^>=high(cardinal)div 10 then begin + repeat // 64-bit loop c := byte(P^); if c=0 then begin err := 0; // conversion success without error @@ -32130,6 +31620,7 @@ function GetQWord(P: PUTF8Char; var err: integer): QWord; break; end; until false; + {$endif CPU64} end; function GetExtended(P: PUTF8Char): TSynExtended; @@ -32140,129 +31631,123 @@ function GetExtended(P: PUTF8Char): TSynExtended; result := 0; end; -{$ifdef PUREPASCAL} - {$define GETEXTENDEDPASCAL} -{$endif} -{$ifdef FPC} - {$define GETEXTENDEDPASCAL} -{$endif} -{$ifdef PIC} - {$define GETEXTENDEDPASCAL} -{$endif} +function HugePower10(exponent: integer): TSynExtended; {$ifdef HASINLINE}inline;{$endif} +var pow10: TSynExtended; +begin + result := 1.0; + if exponent<0 then begin + pow10 := 0.1; + exponent := -exponent; + end else + pow10 := 10; + repeat + while exponent and 1=0 do begin + exponent := exponent shr 1; + pow10 := sqr(pow10); + end; + result := result*pow10; + dec(exponent); + until exponent=0; +end; function GetExtended(P: PUTF8Char; out err: integer): TSynExtended; -// inspired by ValExt_JOH_PAS_8_a and ValExt_JOH_IA32_8_a by John O'Harrow -{$ifdef GETEXTENDEDPASCAL} - const POW10: array[-31..31] of TSynExtended = ( - 1E-31,1E-30,1E-29,1E-28,1E-27,1E-26,1E-25,1E-24,1E-23,1E-22,1E-21,1E-20, - 1E-19,1E-18,1E-17,1E-16,1E-15,1E-14,1E-13,1E-12,1E-11,1E-10,1E-9,1E-8,1E-7, - 1E-6,1E-5,1E-4,1E-3,1E-2,1E-1,1E0,1E1,1E2,1E3,1E4,1E5,1E6,1E7,1E8,1E9,1E10, - 1E11,1E12,1E13,1E14,1E15,1E16,1E17,1E18,1E19,1E20,1E21,1E22,1E23,1E24,1E25, - 1E26,1E27,1E28,1E29,1E30,1E31); - function IntPower(Exponent: Integer): TSynExtended; - var Y: Cardinal; - LBase: Int64; - begin - Y := abs(Exponent); - LBase := 10; - result := 1.0; - repeat - while not odd(Y) do begin - Y := Y shr 1; - LBase := LBase*LBase; - end; - dec(Y); - result := result*LBase; - until Y=0; - if Exponent<0 then - result := 1.0/result; - end; -var Digits, ExpValue: PtrInt; - Ch: cardinal; - flags: set of (Neg, NegExp, Valid); +{$ifndef CPU32DELPHI} // inspired from ValExt_JOH_PAS_8_a by John O'Harrow +const POW10: array[-31..31] of TSynExtended = ( + 1E-31,1E-30,1E-29,1E-28,1E-27,1E-26,1E-25,1E-24,1E-23,1E-22,1E-21,1E-20, + 1E-19,1E-18,1E-17,1E-16,1E-15,1E-14,1E-13,1E-12,1E-11,1E-10,1E-9,1E-8,1E-7, + 1E-6,1E-5,1E-4,1E-3,1E-2,1E-1,1E0,1E1,1E2,1E3,1E4,1E5,1E6,1E7,1E8,1E9,1E10, + 1E11,1E12,1E13,1E14,1E15,1E16,1E17,1E18,1E19,1E20,1E21,1E22,1E23,1E24,1E25, + 1E26,1E27,1E28,1E29,1E30,1E31); +var digits, exp: PtrInt; + ch: byte; + flags: set of (fNeg, fNegExp, fValid); U: PByte; // Delphi Win64 doesn't like if P^ is used directly -{$ifdef CPUX86} -const ten: double = 10.0; // fast copy on x87 stack +{$ifndef CPUX86}ten: TSynExtended;{$endif} // stored in (e.g. xmm2) register begin + {$ifndef CPUX86} ten := 10.0; {$endif} result := 0; -{$else} - ten: TSynExtended; // stored in a local floating-point (e.g. xmm) register -begin - ten := 10.0; - PInt64(@result)^ := 0; -{$endif} if P=nil then begin err := 1; exit; end; - flags := []; + byte(flags) := 0; U := pointer(P); - while U^=32 do - inc(U); - Ch := U^; - if Ch=ord('+') then + if P^=' ' then + repeat + inc(U) + until U^<>32; // trailing spaces + ch := U^; + if ch=ord('+') then inc(U) else - if Ch=ord('-') then begin + if ch=ord('-') then begin inc(U); - include(flags,Neg); + include(flags,fNeg); end; - while true do begin - Ch := U^; + repeat + ch := U^; inc(U); - if (Chord('9')) then + if (chord('9')) then break; - dec(Ch,ord('0')); - result := (result*ten)+Ch; - include(flags,Valid); - end; - Digits := 0; - if Ch=ord('.') then begin - while true do begin - Ch := U^; + dec(ch,ord('0')); + {$ifdef CPUX86} + result := (result*10.0)+ch; + {$else} + result := result*ten; // better FPC+Delphi64 code generation in two steps + result := result+ch; + {$endif} + include(flags,fValid); + until false; + digits := 0; + if ch=ord('.') then + repeat + ch := U^; inc(U); - if (Chord('9')) then begin - if not (Valid in flags) then // starts with '.' - if Ch=0 then - dec(U); // U='.' + if (chord('9')) then begin + if not(fValid in flags) then // starts with '.' + if ch=0 then + dec(U); // U^='.' break; end; - dec(Ch,ord('0')); - result := (result*ten)+Ch; - dec(Digits); - include(flags,Valid); - end; - end; - ExpValue := 0; - if (Ch=ord('E')) or (Ch=ord('e')) then begin - exclude(flags,Valid); - Ch := U^; - if Ch=ord('+') then + dec(ch,ord('0')); + {$ifdef CPUX86} + result := (result*10.0)+ch; + {$else} + result := result*ten; + result := result+ch; + {$endif} + dec(digits); + include(flags,fValid); + until false; + if (ch=ord('E')) or (ch=ord('e')) then begin + exp := 0; + exclude(flags,fValid); + ch := U^; + if ch=ord('+') then inc(U) else - if Ch=ord('-') then begin + if ch=ord('-') then begin inc(U); - include(flags,NegExp); + include(flags,fNegExp); end; - while true do begin - Ch := U^; + repeat + ch := U^; inc(U); - if (Chord('9')) then + if (chord('9')) then break; - dec(Ch,ord('0')); - ExpValue := (ExpValue*10)+PtrInt(Ch); - include(flags,Valid); - end; - if NegExp in flags then - ExpValue := -ExpValue; - end; - inc(Digits,ExpValue); - case Digits of - 0: ; - low(POW10)..-1,1..high(POW10): result := result*POW10[Digits]; - else result := result*IntPower(Digits); - end; - if Neg in flags then + dec(ch,ord('0')); + exp := (exp*10)+PtrInt(ch); + include(flags,fValid); + until false; + if fNegExp in flags then + dec(digits,exp) else + inc(digits,exp); + end; + if digits<>0 then + if (digits>=low(POW10)) and (digits<=high(POW10)) then + result := result*POW10[digits] else + result := result*HugePower10(digits); + if fNeg in flags then result := -result; - if (Valid in flags) and (Ch=0) then + if (fValid in flags) and (ch=0) then err := 0 else err := PUTF8Char(U)-P+1; end; @@ -32387,7 +31872,7 @@ function GetExtended(P: PUTF8Char; out err: integer): TSynExtended; fchs // yes. negate result in fpu jmp @exit // exit setting result code end; -{$endif} +{$endif CPU32DELPHI} function GetUTF8Char(P: PUTF8Char): cardinal; begin @@ -32476,16 +31961,16 @@ function IdemPCharWithoutWhiteSpace(p: PUTF8Char; up: PAnsiChar): boolean; function IdemPCharArray(p: PUTF8Char; const upArray: array of PAnsiChar): integer; var w: word; - tab: {$ifdef CPUX86}TNormTableByte absolute NormToUpperAnsi7{$else}PNormTableByte{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTableByte absolute NormToUpperAnsi7{$else}PNormTableByte{$endif}; up: ^PAnsiChar; begin if p<>nil then begin - {$ifndef CPUX86}tab := @NormToUpperAnsi7;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @NormToUpperAnsi7;{$endif} // faster on PIC and x86_64 w := tab[ord(p[0])]+tab[ord(p[1])]shl 8; up := @upArray[0]; for result := 0 to high(upArray) do if (PWord(up^)^=w) and - {$ifdef CPUX86}IdemPChar({$else}IdemPChar2(pointer(tab),{$endif}p+2,up^+2) then + {$ifdef CPUX86NOTPIC}IdemPChar({$else}IdemPChar2(pointer(tab),{$endif}p+2,up^+2) then exit else inc(up); end; @@ -32518,14 +32003,14 @@ function IdemPCharU(p, up: PUTF8Char): boolean; end; function EndWith(const text, upText: RawUTF8): boolean; -var o: integer; +var o: PtrInt; begin o := length(text)-length(upText); result := (o>=0) and IdemPChar(PUTF8Char(pointer(text))+o,pointer(upText)); end; function EndWithArray(const text: RawUTF8; const upArray: array of RawUTF8): integer; -var t,o: integer; +var t,o: PtrInt; begin t := length(text); if t>0 then @@ -32577,14 +32062,14 @@ function UpperCopy255BufPas(dest: PAnsiChar; source: PUTF8Char; sourceLen: PtrIn function UpperCopyWin255(dest: PWinAnsiChar; const source: RawUTF8): PWinAnsiChar; var i, L: integer; - tab: {$ifdef CPUX86}TNormTableByte absolute NormToUpperByte{$else}PNormTableByte{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTableByte absolute NormToUpperByte{$else}PNormTableByte{$endif}; begin L := {$ifdef FPC}_LStrLen(source){$else}PInteger(PtrInt(source)-SizeOf(integer))^{$endif}; if L>0 then begin if L>250 then L := 250; // avoid buffer overflow result := dest+L; - {$ifndef CPUX86}tab := @NormToUpperByte;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @NormToUpperByte;{$endif} // faster on PIC and x86_64 for i := 0 to L-1 do dest[i] := AnsiChar(tab[PByteArray(source)[i]]); end else @@ -32641,7 +32126,7 @@ function UTF8UpperCopy(Dest, Source: PUTF8Char; SourceChars: Cardinal): PUTF8Cha S := Source-1; // leave UTF-8 encoding untouched inc(Source,extra); inc(extra); - MoveFast(S^,Dest^,extra); + {$ifdef FPC}Move{$else}MoveFast{$endif}(S^,Dest^,extra); inc(Dest,extra); if (PtrUInt(Source) and 3=0) and (Sourcenil then @@ -32835,17 +32320,120 @@ function GotoNextLine(source: PUTF8Char): PUTF8Char; until false else result := source; end; -{$WARNINGS ON} +{$ifdef FPC}{$pop}{$else}{$WARNINGS ON}{$endif} + +function BufferLineLength(Text, TextEnd: PUTF8Char): PtrInt; +{$ifdef CPUX64} +{$ifdef FPC} nostackframe; assembler; asm {$else} asm .NOFRAME {$endif} +{$ifdef MSWINDOWS} // Win64 ABI to System-V ABI + push rsi + push rdi + mov rdi, rcx + mov rsi, rdx +{$endif}mov r8, rsi + sub r8, rdi // rdi=Text, rsi=TextEnd, r8=TextLen + jz @fail + mov ecx, edi + movdqa xmm0, [rip + @for10] + movdqa xmm1, [rip + @for13] + and rdi, -16 // check first aligned 16 bytes + and ecx, 15 // lower 4 bits indicate misalignment + movdqa xmm2, [rdi] + movdqa xmm3, xmm2 + pcmpeqb xmm2, xmm0 + pcmpeqb xmm3, xmm1 + por xmm3, xmm2 + pmovmskb eax, xmm3 + shr eax, cl // shift out unaligned bytes + test eax, eax + jz @main + bsf eax, eax + add rax, rcx + add rax, rdi + sub rax, rsi + jae @fail // don't exceed TextEnd + add rax, r8 // rax = TextFound - TextEnd + (TextEnd - Text) = offset +{$ifdef MSWINDOWS} + pop rdi + pop rsi +{$endif}ret +@main: add rdi, 16 + sub rdi, rsi + jae @fail + jmp @by16 +{$ifdef FPC} align 16 {$else} .align 16 {$endif} +@for10: dq $0a0a0a0a0a0a0a0a + dq $0a0a0a0a0a0a0a0a +@for13: dq $0d0d0d0d0d0d0d0d + dq $0d0d0d0d0d0d0d0d +@by16: movdqa xmm2, [rdi + rsi] // check 16 bytes per loop + movdqa xmm3, xmm2 + pcmpeqb xmm2, xmm0 + pcmpeqb xmm3, xmm1 + por xmm3, xmm2 + pmovmskb eax, xmm3 + test eax, eax + jnz @found + add rdi, 16 + jnc @by16 +@fail: mov rax, r8 // returns TextLen if no CR/LF found +{$ifdef MSWINDOWS} + pop rdi + pop rsi +{$endif}ret +@found: bsf eax, eax + add rax, rdi + jc @fail + add rax, r8 +{$ifdef MSWINDOWS} + pop rdi + pop rsi +{$endif} +end; +{$else} {$ifdef FPC}inline;{$endif} +var c: cardinal; +begin + result := 0; + dec(PtrInt(TextEnd),PtrInt(Text)); // compute TextLen + if TextEnd<>nil then + repeat + c := ord(Text[result]); + if c>13 then begin + inc(result); + if result>=PtrInt(TextEnd) then + break; + continue; + end; + if (c=10) or (c=13) then + break; + inc(result); + if result>=PtrInt(TextEnd) then + break; + until false; +end; +{$endif CPUX64} function GetLineSize(P,PEnd: PUTF8Char): PtrUInt; +var c: cardinal; begin + if PEnd=nil then + dec(PtrUInt(PEnd)); result := PtrUInt(P); if P<>nil then - if PEnd=nil then - while P^ in ANSICHARNOT01310 do - inc(P) else - while (P13 then begin inc(P); + if P>=PEnd then + break; + continue; + end; + if (c=0) or (c=10) or (c=13) then + break; + inc(P); + if P>=PEnd then + break; + until false; result := PtrUInt(P)-result; end; @@ -32869,17 +32457,31 @@ procedure GetNextItem(var P: PUTF8Char; Sep: AnsiChar; var result: RawUTF8); end; end; +procedure GetNextItem(var P: PUTF8Char; Sep, Quote: AnsiChar; var result: RawUTF8); +begin + if P=nil then + result := '' + else if P^=Quote then begin + P := UnQuoteSQLStringVar(P,result); + if P=nil then + result := '' + else if P^<>#0 then + inc(P); + end else + GetNextItem(P,Sep,result); +end; + procedure GetNextItemTrimed(var P: PUTF8Char; Sep: AnsiChar; var result: RawUTF8); var S,E: PUTF8Char; begin if (P=nil) or (Sep<=' ') then result := '' else begin - while (P^<=' ') and (P^<>#0) do inc(P); + while (P^<=' ') and (P^<>#0) do inc(P); // trim left S := P; while (S^<>#0) and (S^<>Sep) do inc(S); E := S; - while (E>P) and (E[-1] in [#1..' ']) do dec(E); + while (E>P) and (E[-1] in [#1..' ']) do dec(E); // trim right FastSetString(result,P,E-P); if S^<>#0 then P := S+1 else @@ -33084,11 +32686,11 @@ function CSVOfValue(const Value: RawUTF8; Count: cardinal; const Sep: RawUTF8): P := pointer(result); i := 1; repeat - MoveFast(Pointer(Value)^,P^,ValueLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Pointer(Value)^,P^,ValueLen); inc(P,ValueLen); if i=Count then break; - MoveFast(Pointer(Sep)^,P^,SepLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Pointer(Sep)^,P^,SepLen); inc(P,SepLen); inc(i); until false; @@ -33103,14 +32705,14 @@ procedure SetBitCSV(var Bits; BitsCount: integer; var P: PUTF8Char); if bit>=cardinal(BitsCount) then break; // avoid GPF if (P=nil) or (P^=',') then - SetBit(Bits,bit) else + SetBitPtr(@Bits,bit) else if P^='-' then begin inc(P); last := GetNextItemCardinalStrict(P)-1; // '0' marks end of list if last>=Cardinal(BitsCount) then exit; while bit<=last do begin - SetBit(Bits,bit); + SetBitPtr(@Bits,bit); inc(bit); end; end; @@ -33127,9 +32729,9 @@ function GetBitCSV(const Bits; BitsCount: integer): RawUTF8; result := ''; i := 0; while i0) and (L and 1=0) then + if not HexDisplayToBin(@tmp,@result,L shr 1) then + result := 0; +end; + function GetNextItemDouble(var P: PUTF8Char; Sep: AnsiChar): double; var tmp: TChar64; err: integer; @@ -33275,6 +32888,15 @@ function GetCSVItem(P: PUTF8Char; Index: PtrUInt; Sep: AnsiChar): RawUTF8; GetNextItem(P,Sep,result); end; +function GetUnQuoteCSVItem(P: PUTF8Char; Index: PtrUInt; Sep, Quote: AnsiChar): RawUTF8; +var i: PtrUInt; +begin + if P=nil then + result := '' else + for i := 0 to Index do + GetNextItem(P,Sep,Quote,result); +end; + function GetLastCSVItem(const CSV: RawUTF8; Sep: AnsiChar): RawUTF8; var i: integer; begin @@ -33418,13 +33040,13 @@ function RawUTF8ArrayToCSV(const Values: array of RawUTF8; const Sep: RawUTF8): repeat L := length(Values[i]); if L>0 then begin - MoveFast(pointer(Values[i])^,P^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Values[i])^,P^,L); inc(P,L); end; if i=high(Values) then Break; if seplen>0 then begin - MoveFast(pointer(Sep)^,P^,seplen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Sep)^,P^,seplen); inc(P,seplen); end; inc(i); @@ -33806,8 +33428,8 @@ function UrlDecodeNextNameValue(U: PUTF8Char; var Name,Value: RawUTF8): PUTF8Cha result := U+1; // jump '&' to let decode the next name=value pair end; -function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8; - Next: PPUTF8Char=nil): boolean; +function UrlDecodeValue(U: PUTF8Char; const Upper: RawUTF8; var Value: RawUTF8; + Next: PPUTF8Char): boolean; begin // UrlDecodeValue('select=%2A&where=LastName%3D%27M%C3%B4net%27','SELECT=',V,@U) // -> U^='where=...' and V='*' @@ -33817,9 +33439,9 @@ function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8; Next^ := U; exit; end; - if IdemPChar(U,Upper) then begin + if IdemPChar(U,pointer(Upper)) then begin result := true; - inc(U,StrLen(PUTF8Char(Upper))); + inc(U,length(Upper)); U := UrlDecodeNextValue(U,Value); end; if Next=nil then @@ -33830,8 +33452,8 @@ function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8; Next^ := U+1; // jump '&' end; -function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar; - var Value: integer; Next: PPUTF8Char=nil): boolean; +function UrlDecodeInteger(U: PUTF8Char; const Upper: RawUTF8; + var Value: integer; Next: PPUTF8Char): boolean; var V: PtrInt; SignNeg: boolean; begin @@ -33843,8 +33465,8 @@ function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar; Next^ := U; exit; end; - if IdemPChar(U,Upper) then begin - inc(U,StrLen(PUTF8Char(Upper))); + if IdemPChar(U,pointer(Upper)) then begin + inc(U,length(Upper)); if U^='-' then begin SignNeg := True; Inc(U); @@ -33870,7 +33492,8 @@ function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar; Next^ := U+1; // jump '&' end; -function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal; Next: PPUTF8Char=nil): boolean; +function UrlDecodeCardinal(U: PUTF8Char; const Upper: RawUTF8; + var Value: Cardinal; Next: PPUTF8Char): boolean; var V: PtrInt; begin // UrlDecodeInteger('offset=20&where=LastName%3D%27M%C3%B4net%27','OFFSET=',O,@Next) @@ -33881,8 +33504,8 @@ function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal; N Next^ := U; exit; end; - if IdemPChar(U,Upper) then begin - inc(U,StrLen(PUTF8Char(Upper))); + if IdemPChar(U,pointer(Upper)) then begin + inc(U,length(Upper)); if U^ in ['0'..'9'] then begin V := 0; repeat @@ -33901,21 +33524,21 @@ function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal; N Next^ := U+1; // jump '&' end; -function UrlDecodeInt64(U: PUTF8Char; Upper: PAnsiChar; - var Value: Int64; Next: PPUTF8Char=nil): boolean; +function UrlDecodeInt64(U: PUTF8Char; const Upper: RawUTF8; + var Value: Int64; Next: PPUTF8Char): boolean; var tmp: RawUTF8; begin - result := UrlDecodeValue(U, Upper, tmp, Next); + result := UrlDecodeValue(U,Upper,tmp,Next); if result then SetInt64(pointer(tmp),Value); end; -function UrlDecodeExtended(U: PUTF8Char; Upper: PAnsiChar; +function UrlDecodeExtended(U: PUTF8Char; const Upper: RawUTF8; var Value: TSynExtended; Next: PPUTF8Char=nil): boolean; var tmp: RawUTF8; err: integer; begin - result := UrlDecodeValue(U, Upper, tmp, Next); + result := UrlDecodeValue(U,Upper,tmp,Next); if result then begin Value := GetExtended(pointer(tmp),err); if err<>0 then @@ -33923,12 +33546,12 @@ function UrlDecodeExtended(U: PUTF8Char; Upper: PAnsiChar; end; end; -function UrlDecodeDouble(U: PUTF8Char; Upper: PAnsiChar; var Value: double; +function UrlDecodeDouble(U: PUTF8Char; const Upper: RawUTF8; var Value: double; Next: PPUTF8Char=nil): boolean; var tmp: RawUTF8; err: integer; begin - result := UrlDecodeValue(U, Upper, tmp, Next); + result := UrlDecodeValue(U,Upper,tmp,Next); if result then begin Value := GetExtended(pointer(tmp),err); if err<>0 then @@ -34075,12 +33698,12 @@ procedure FillZero(var Values: TRawUTF8DynArray); procedure FillZero(var Values: TIntegerDynArray); begin - FillCharFast(Values[0],length(Values)*SizeOf(integer),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Values[0],length(Values)*SizeOf(integer),0); end; procedure FillZero(var Values: TInt64DynArray); begin - FillCharFast(Values[0],length(Values)*SizeOf(Int64),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Values[0],length(Values)*SizeOf(Int64),0); end; @@ -34103,28 +33726,28 @@ function Hash32(const Text: RawByteString): cardinal; result := Hash32(pointer(Text),length(Text)); end; -function Hash32(Data: pointer; Len: integer): cardinal; +function Hash32(Data: PCardinalArray; Len: integer): cardinal; var s1,s2: cardinal; - i: PtrInt; + i: integer; begin if Data<>nil then begin s1 := 0; s2 := 0; - for i := 1 to Len shr 4 do begin // 16 bytes (4 DWORD) by loop - aligned read - inc(s1,PCardinalArray(Data)^[0]); + for i := 1 to Len shr 4 do begin // 16 bytes (128-bit) loop - aligned read + inc(s1,Data[0]); inc(s2,s1); - inc(s1,PCardinalArray(Data)^[1]); + inc(s1,Data[1]); inc(s2,s1); - inc(s1,PCardinalArray(Data)^[2]); + inc(s1,Data[2]); inc(s2,s1); - inc(s1,PCardinalArray(Data)^[3]); + inc(s1,Data[3]); inc(s2,s1); - inc(PByte(Data),16); + Data := @Data[4]; end; for i := 1 to (Len shr 2)and 3 do begin // 4 bytes (DWORD) by loop - inc(s1,PCardinalArray(Data)^[0]); + inc(s1,Data[0]); inc(s2,s1); - inc(PByte(Data),4); + Data := @Data[1]; end; case Len and 3 of // remaining 0..3 bytes 1: inc(s1,PByte(Data)^); @@ -34137,91 +33760,6 @@ function Hash32(Data: pointer; Len: integer): cardinal; result := 0; end; -function GetBit(const Bits; aIndex: PtrInt): boolean; -{$ifdef CPUINTEL}{$ifdef CPU64}{$ifdef FPC}nostackframe;assembler;asm{$else}asm .noframe{$endif}{$else}asm{$endif} - bt [Bits], aIndex - sbb eax, eax - and eax, 1 -end; -{$else} -begin - result := TIntegerArray(Bits)[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0; -end; -{$endif CPUINTEL} - -function GetAllBits(Bits: Cardinal; BitCount: Integer): boolean; -begin - if BitCount in [low(ALLBITS_CARDINAL)..high(ALLBITS_CARDINAL)] then - result := (Bits and ALLBITS_CARDINAL[BitCount])=ALLBITS_CARDINAL[BitCount] else - result := false; -end; - -procedure SetBit(var Bits; aIndex: PtrInt); -{$ifdef CPUINTEL}{$ifdef CPU64}{$ifdef FPC}nostackframe;assembler;asm{$else}asm .noframe{$endif}{$else}asm{$endif} - bts [Bits], aIndex -end; -{$else} -begin - TIntegerArray(Bits)[aIndex shr 5] := TIntegerArray(Bits)[aIndex shr 5] - or (1 shl (aIndex and 31)); -end; -{$endif CPUINTEL} - -procedure UnSetBit(var Bits; aIndex: PtrInt); -{$ifdef CPUINTEL}{$ifdef CPU64}{$ifdef FPC}nostackframe;assembler;asm{$else}asm .noframe{$endif}{$else}asm{$endif} - btr [Bits], aIndex -end; -{$else} -begin - PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5] - and not (1 shl (aIndex and 31)); -end; -{$endif CPUINTEL} - -function GetBit64(const Bits: Int64; aIndex: PtrInt): boolean; -begin - result := aIndex in TBits64(Bits); -end; - -procedure SetBit64(var Bits: Int64; aIndex: PtrInt); -begin - include(PBits64(@Bits)^,aIndex); -end; - -procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt); -begin - exclude(PBits64(@Bits)^,aIndex); -end; - -function GetBitsCount(const Bits; Count: PtrInt): integer; -const POPCNTDATA: array[0..15+4] of integer = (0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,0,1,3,7); -var P: PByte; - v: PtrUInt; -{$ifdef CPUX86}tab: TIntegerArray absolute POPCNTDATA; -begin // not enough registers on this CPU -{$else}tab: PIntegerArray; -begin - tab := @POPCNTDATA; -{$endif CPUX86} - P := @Bits; - result := 0; - while Count>=8 do begin - dec(Count,8); - v := P^; - inc(result,tab[v and $f]); - inc(result,tab[v shr 4]); - inc(P); - end; - v := P^; - if Count>=4 then begin - dec(Count,4); - inc(result,tab[v and $f]); - v := v shr 4; - end; - if Count>0 then - inc(result,tab[v and tab[Count+16]]); -end; - procedure OrMemory(Dest,Source: PByteArray; size: PtrInt); begin while size>=SizeOf(PtrInt) do begin @@ -34283,6 +33821,7 @@ procedure AndMemory(Dest,Source: PByteArray; size: PtrInt); {$ifdef CPUX86} function xxHash32(crc: cardinal; P: PAnsiChar; len: integer): cardinal; +{$ifdef FPC}nostackframe; assembler;{$endif} asm xchg edx, ecx push ebp @@ -34384,7 +33923,7 @@ function xxHash32(crc: cardinal; P: PAnsiChar; len: integer): cardinal; {$ifdef CPUX64} function xxHash32(crc: cardinal; P: PAnsiChar; len: integer): cardinal; -asm +{$ifdef FPC}nostackframe; assembler; asm {$else} asm .noframe{$endif} {$ifdef LINUX} // crc=rdi P=rsi len=rdx mov r8, rdi mov rcx, rsi @@ -34571,8 +34110,7 @@ TRegisters = record {$ifdef CPU64} procedure GetCPUID(Param: Cardinal; var Registers: TRegisters); {$ifdef FPC}nostackframe; assembler; asm {$else} -asm // ecx=param, rdx=Registers (Linux: edi,rsi) - .noframe +asm .noframe // ecx=param, rdx=Registers (Linux: edi,rsi) {$endif FPC} {$ifdef win64} mov eax, ecx @@ -34600,8 +34138,7 @@ procedure GetCPUID(Param: Cardinal; var Registers: TRegisters); function UpperCopy255BufSSE42(dest: PAnsiChar; source: PUTF8Char; sourceLen: PtrInt): PAnsiChar; {$ifdef FPC}nostackframe; assembler; asm {$else} -asm // rcx=dest, rdx=source, r8=len (Linux: rdi,rsi,rdx) - .noframe +asm .noframe // rcx=dest, rdx=source, r8=len (Linux: rdi,rsi,rdx) {$endif FPC} {$ifdef win64} mov rax, rcx @@ -34655,8 +34192,7 @@ function UpperCopy255BufSSE42(dest: PAnsiChar; source: PUTF8Char; sourceLen: Ptr function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; {$ifdef FPC}nostackframe; assembler; asm {$else} -asm // ecx=crc, rdx=buf, r8=len (Linux: edi,rsi,rdx) - .noframe +asm .noframe // ecx=crc, rdx=buf, r8=len (Linux: edi,rsi,rdx) {$endif FPC} {$ifdef win64} mov eax, ecx @@ -34711,10 +34247,8 @@ function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; function StrLenSSE2(S: pointer): PtrInt; {$ifdef FPC}nostackframe; assembler; asm {$else} -asm // rcx=S (Linux: rdi) - .noframe -{$endif FPC} - // from GPL strlen64.asm by Agner Fog - www.agner.org/optimize +asm .noframe // rcx=S (Linux: rdi) +{$endif FPC} // from GPL strlen64.asm by Agner Fog - www.agner.org/optimize {$ifdef win64} mov rax, rcx // get pointer to string from rcx mov r8, rcx // copy pointer @@ -34725,10 +34259,11 @@ function StrLenSSE2(S: pointer): PtrInt; test rdi, rdi {$endif} jz @null // returns 0 if S=nil - // rax = s,ecx = 32 bits of s + // rax=s,ecx=32-bit of s pxor xmm0, xmm0 // set to zero and ecx, 15 // lower 4 bits indicate misalignment and rax, -16 // align pointer by 16 + // will never read outside a memory page boundary, so won't trigger GPF movdqa xmm1, [rax] // read from nearest preceding boundary pcmpeqb xmm1, xmm0 // compare 16 bytes with zero pmovmskb edx, xmm1 // get one bit for each byte result @@ -34800,7 +34335,7 @@ function StrCompSSE42(Str1, Str2: pointer): PtrInt; {$else} mov rax, rdi mov rdx, rsi - test rdi, rsi + test rdi, rsi // is one of Str1/Str2 nil ? {$endif} jz @n @ok: sub rax, rdx @@ -34835,39 +34370,70 @@ function StrCompSSE42(Str1, Str2: pointer): PtrInt; sub rax, rdx end; {$endif HASAESNI} - {$endif CPU64} {$endif CPUINTEL} procedure crcblocks(crc128, data128: PBlock128; count: integer); +var oneblock: procedure(crc128, data128: PBlock128); + i: integer; begin - {$ifdef CPUX86} - if (cfSSE42 in CpuFeatures) and (count>0) then - asm - mov ecx, crc128 - mov edx, data128 -@s: mov eax, dword ptr[ecx] - db $F2, $0F, $38, $F1, $02 - mov dword ptr[ecx], eax - mov eax, dword ptr[ecx + 4] - db $F2, $0F, $38, $F1, $42, $04 - mov dword ptr[ecx + 4], eax - mov eax, dword ptr[ecx + 8] - db $F2, $0F, $38, $F1, $42, $08 - mov dword ptr[ecx + 8], eax - mov eax, dword ptr[ecx + 12] - db $F2, $0F, $38, $F1, $42, $0C - mov dword ptr[ecx + 12], eax - add edx, 16 - dec count - jnz @s - end else - {$endif CPUX86} - while count>0 do begin - crcblock(crc128,data128); - inc(data128); - dec(count); - end; + if count>0 then + {$ifndef DISABLE_SSE42} + {$ifdef CPUX86} + if cfSSE42 in CpuFeatures then + asm + mov ecx, crc128 + mov edx, data128 + @s: mov eax, dword ptr[ecx] + db $F2, $0F, $38, $F1, $02 // crc32 eax, dword ptr [edx] + mov dword ptr[ecx], eax + mov eax, dword ptr[ecx + 4] + db $F2, $0F, $38, $F1, $42, $04 // crc32 eax, dword ptr [edx+4] + mov dword ptr[ecx + 4], eax + mov eax, dword ptr[ecx + 8] + db $F2, $0F, $38, $F1, $42, $08 // crc32 eax, dword ptr [edx+8] + mov dword ptr[ecx + 8], eax + mov eax, dword ptr[ecx + 12] + db $F2, $0F, $38, $F1, $42, $0C // crc32 eax, dword ptr [edx+12] + mov dword ptr[ecx + 12], eax + add edx, 16 + dec count + jnz @s + end else + {$endif CPUX86} + {$ifdef CPUX64} + {$ifdef FPC} // only FPC is able to compile such inlined asm block + if cfSSE42 in CpuFeatures then + asm + mov rax, data128 + mov rdx, crc128 + mov ecx, count + mov r8d, dword ptr [rdx] + mov r9d, dword ptr [rdx + 4] + mov r10d, dword ptr [rdx + 8] + mov r11d, dword ptr [rdx + 12] + align 8 +@s: crc32 r8d, dword ptr [rax] + crc32 r9d, dword ptr [rax + 4] + crc32 r10d, dword ptr [rax + 8] + crc32 r11d, dword ptr [rax + 12] + add rax, 16 + dec ecx + jnz @s + mov dword ptr [rdx], r8d + mov dword ptr [rdx + 4], r9d + mov dword ptr [rdx + 8], r10d + mov dword ptr [rdx + 12], r11d + end else + {$endif FPC} + {$endif CPUX64} + {$endif DISABLE_SSE42} begin + oneblock := @crcblock; + for i := 1 to count do begin + oneblock(crc128,data128); + inc(data128); + end; + end; end; {$ifdef CPUINTEL} @@ -34884,7 +34450,7 @@ function crc32cBy4SSE42(crc, value: cardinal): cardinal; crc32 eax, edx {$endif} end; -{$else} +{$else} {$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=crc, edx=value {$ifdef FPC_OR_UNICODE} crc32 eax, edx @@ -34927,7 +34493,7 @@ procedure crcblockSSE42(crc128, data128: PBlock128); mov dword ptr[rcx + 12], r10d {$endif Linux} end; -{$else} +{$else} {$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=crc128, edx=data128 mov ecx, eax {$ifdef FPC_OR_UNICODE} @@ -34995,6 +34561,7 @@ function crc32cinlined(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; {$ifdef CPUX86} procedure GetCPUID(Param: Cardinal; var Registers: TRegisters); +{$ifdef FPC}nostackframe; assembler;{$endif} asm push esi push edi @@ -35029,6 +34596,7 @@ procedure GetCPUID(Param: Cardinal; var Registers: TRegisters); end; function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal; +{$ifdef FPC}nostackframe; assembler;{$endif} asm // eax=crc, edx=buf, ecx=len not eax test ecx, ecx @@ -35192,6 +34760,27 @@ function IP6Text(ip6: PHash128): shortstring; IP6Text(ip6, @result); end; +function IsZero(const dig: THash160): boolean; +var a: TIntegerArray absolute dig; +begin + result := (a[0]=0) and (a[1]=0) and (a[2]=0) and (a[3]=0) and (a[4]=0); +end; + +function IsEqual(const A,B: THash160): boolean; +var a_: TIntegerArray absolute A; + b_: TIntegerArray absolute B; +begin // uses anti-forensic time constant "xor/or" pattern + result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or + (a_[3] xor b_[3]) or (a_[4] xor b_[4]))=0; +end; + +procedure FillZero(out dig: THash160); +begin + PInt64Array(@dig)^[0] := 0; + PInt64Array(@dig)^[1] := 0; + PIntegerArray(@dig)^[4] := 0; +end; + procedure crc256c(buf: PAnsiChar; len: cardinal; out crc: THash256); var h: THash256Rec absolute crc; h1,h2: cardinal; @@ -35327,16 +34916,16 @@ procedure FillZero(var secret: RawByteString); begin if secret<>'' then with PStrRec(Pointer(PtrInt(secret)-STRRECSIZE))^ do - if refCnt>0 then // avoid GPF if const - FillcharFast(pointer(secret)^,length,0); + if refCnt=1 then // avoid GPF if const + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(pointer(secret)^,length,0); end; procedure FillZero(var secret: RawUTF8); begin if secret<>'' then with PStrRec(Pointer(PtrInt(secret)-STRRECSIZE))^ do - if refCnt>0 then // avoid GPF if const - FillcharFast(pointer(secret)^,length,0); + if refCnt=1 then // avoid GPF if const + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(pointer(secret)^,length,0); end; procedure mul64x64(const left, right: QWord; out product: THash128Rec); @@ -35353,7 +34942,7 @@ procedure mul64x64(const left, right: QWord; out product: THash128Rec); mov qword ptr [r8+8], rdx end; {$else} -{$ifdef CPUX86} +{$ifdef CPU32DELPHI} asm // adapted from FPC compiler output, which is much better than Delphi's here mov ecx, eax mov eax, dword ptr [ebp+8H] @@ -35392,7 +34981,7 @@ procedure mul64x64(const left, right: QWord; out product: THash128Rec); product.H := QWord(l.H)*r.H+t2.H+t3.H; product.L := t3.V shl 32 or t1.L; end; -{$endif CPUX86} +{$endif CPU32DELPHI} {$endif CPUX64} procedure SymmetricEncrypt(key: cardinal; var data: RawByteString); @@ -35418,7 +35007,7 @@ procedure SymmetricEncrypt(key: cardinal; var data: RawByteString); function UnixTimeToDateTime(const UnixTime: TUnixTime): TDateTime; begin - result := (UnixTime / SecsPerDay + UnixDateDelta); + result := UnixTime / SecsPerDay + UnixDateDelta; end; function DateTimeToUnixTime(const AValue: TDateTime): TUnixTime; @@ -35504,51 +35093,34 @@ function UnixTimeToString(const UnixTime: TUnixTime; Expanded: boolean; FirstTimeChar,false); end; -{$ifdef FPC} // faster than current RTL version -procedure DecodeTime(dt: TDateTime; out HH, MM, SS, MS: word); -var t, t2: cardinal; -begin - t := round(abs(dt) * MSecsPerDay) mod MSecsPerDay; - t2 := t div 3600000; - HH := t2; - dec(t, t2 * 3600000); - t2 := t div 60000; - MM := t2; - dec(t, t2 * 60000); - t2 := t div 1000; - SS := t2; - MS := t - t2 * 1000; -end; -{$endif FPC} - function DateTimeToFileShort(const DateTime: TDateTime): TShort16; begin DateTimeToFileShort(DateTime,result); end; procedure DateTimeToFileShort(const DateTime: TDateTime; out result: TShort16); -var HH,MM,SS,MS,Y,M,D: word; - tab: {$ifdef CPUX86}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; +var T: TSynSystemTime; + tab: {$ifdef CPUX86NOTPIC}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; begin // use 'YYMMDDHHMMSS' format if DateTime<=0 then begin PWord(@result[0])^ := 1+ord('0') shl 8; exit; end; - DecodeDate(DateTime,Y,M,D); - if Y > 1999 then - if Y < 2100 then - dec(Y,2000) else - Y := 99 else - Y := 0; - DecodeTime(DateTime,HH,MM,SS,MS); - {$ifndef CPUX86}tab := @TwoDigitLookupW;{$endif} + T.FromDate(DateTime); + if T.Year > 1999 then + if T.Year < 2100 then + dec(T.Year,2000) else + T.Year := 99 else + T.Year := 0; + T.FromTime(DateTime); + {$ifndef CPUX86NOTPIC}tab := @TwoDigitLookupW;{$endif} result[0] := #12; - PWord(@result[1])^ := tab[Y]; - PWord(@result[3])^ := tab[M]; - PWord(@result[5])^ := tab[D]; - PWord(@result[7])^ := tab[HH]; - PWord(@result[9])^ := tab[MM]; - PWord(@result[11])^ := tab[SS]; + PWord(@result[1])^ := tab[T.Year]; + PWord(@result[3])^ := tab[T.Month]; + PWord(@result[5])^ := tab[T.Day]; + PWord(@result[7])^ := tab[T.Hour]; + PWord(@result[9])^ := tab[T.Minute]; + PWord(@result[11])^ := tab[T.Second]; end; procedure UnixTimeToFileShort(const UnixTime: TUnixTime; out result: TShort16); @@ -35577,7 +35149,7 @@ function UnixTimePeriodToString(const UnixTime: TUnixTime; FirstTimeChar: AnsiCh function UnixMSTimeToDateTime(const UnixMSTime: TUnixMSTime): TDateTime; begin - result := (UnixMSTime/MSecsPerDay + UnixDateDelta); + result := UnixMSTime/MSecsPerDay + UnixDateDelta; end; function UnixMSTimePeriodToString(const UnixTime: TUnixTime; FirstTimeChar: AnsiChar): RawUTF8; @@ -35589,7 +35161,9 @@ function UnixMSTimePeriodToString(const UnixTime: TUnixTime; FirstTimeChar: Ansi function DateTimeToUnixMSTime(const AValue: TDateTime): TUnixMSTime; begin - result := Round((AValue - UnixDateDelta) * MSecsPerDay); + if AValue=0 then + result := 0 else + result := Round((AValue - UnixDateDelta) * MSecsPerDay); end; function UnixMSTimeToString(const UnixMSTime: TUnixMSTime; Expanded: boolean; @@ -35714,7 +35288,7 @@ procedure Iso8601ToDateTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TD var B: cardinal; Y,M,D, H,MI,SS,MS: cardinal; d100: TDiv100Rec; - tab: {$ifdef CPUX86}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; // expect 'YYYYMMDDThhmmss[.sss]' format but handle also 'YYYY-MM-DDThh:mm:ss[.sss]' begin unaligned(result) := 0; @@ -35728,7 +35302,7 @@ procedure Iso8601ToDateTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TD dec(P,8); inc(L,8); end else begin - {$ifndef CPUX86}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 B := tab[ord(P[0])]; // first digit if B>9 then exit else Y := B; // fast check '0'..'9' B := tab[ord(P[1])]; @@ -35861,58 +35435,64 @@ function TimeLogToUnixTime(const Timestamp: TTimeLog): TUnixTime; result := PTimeLogBits(@Timestamp)^.ToUnixTime; end; -procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: cardinal); +procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: PtrUInt); // use 'YYYYMMDD' format if not Expanded, 'YYYY-MM-DD' format if Expanded +var tab: {$ifdef CPUX86NOTPIC}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; begin + {$ifdef CPUX86NOTPIC} YearToPChar(Y,P); + {$else} + tab := @TwoDigitLookupW; + YearToPChar2(tab,Y,P); + {$endif} inc(P,4); if Expanded then begin P^ := '-'; inc(P); end; - PWord(P)^ := TwoDigitLookupW[M]; + PWord(P)^ := tab[M]; inc(P,2); if Expanded then begin P^ := '-'; inc(P); end; - PWord(P)^ := TwoDigitLookupW[D]; + PWord(P)^ := tab[D]; end; -procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S,MS: cardinal; +procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S,MS: PtrUInt; FirstChar: AnsiChar; WithMS: boolean); -// use Thhmmss[.sss] format -begin +var tab: {$ifdef CPUX86NOTPIC}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; +begin // use Thhmmss[.sss] format if FirstChar<>#0 then begin P^ := FirstChar; inc(P); end; - PWord(P)^ := TwoDigitLookupW[H]; + {$ifndef CPUX86NOTPIC}tab := @TwoDigitLookupW;{$endif} + PWord(P)^ := tab[H]; inc(P,2); if Expanded then begin P^ := ':'; inc(P); end; - PWord(P)^ := TwoDigitLookupW[M]; + PWord(P)^ := tab[M]; inc(P,2); if Expanded then begin P^ := ':'; inc(P); end; - PWord(P)^ := TwoDigitLookupW[S]; + PWord(P)^ := tab[S]; if WithMS then begin inc(P,2); - YearToPChar(MS,P); + {$ifdef CPUX86NOTPIC}YearToPChar(MS{$else}YearToPChar2(tab,MS{$endif},P); P^ := '.'; // override first digit end; end; procedure DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean); -// use YYYYMMDD / YYYY-MM-DD date format -var Y,M,D: word; -begin - DecodeDate(Date,Y,M,D); - DateToIso8601PChar(P,Expanded,Y,M,D); +var T: TSynSystemTime; +begin // use YYYYMMDD / YYYY-MM-DD date format + T.FromDate(Date); + DateToIso8601PChar(P,Expanded,T.Year,T.Month,T.Day); end; function DateToIso8601Text(Date: TDateTime): RawUTF8; @@ -35926,10 +35506,10 @@ function DateToIso8601Text(Date: TDateTime): RawUTF8; procedure TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean; FirstChar: AnsiChar; WithMS: boolean); -var H,M,S,MS: word; +var T: TSynSystemTime; begin - DecodeTime(Time,H,M,S,MS); - TimeToIso8601PChar(P,Expanded,H,M,S,MS,FirstChar,WithMS); + T.FromTime(Time); + TimeToIso8601PChar(P,Expanded,T.Hour,T.Minute,T.Second,T.MilliSecond,FirstChar,WithMS); end; function DateTimeToIso8601(D: TDateTime; Expanded: boolean; @@ -36096,12 +35676,12 @@ procedure TTimeLogBits.From(P: PUTF8Char; L: integer); procedure TTimeLogBits.Expand(out Date: TSynSystemTime); begin Date.Year := (Value shr (6+6+5+5+4)) and 4095; - Date.Month := 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15; - Date.Day := 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31; + Date.Month := 1+(PCardinal(@Value)^ shr (6+6+5+5)) and 15; + Date.Day := 1+(PCardinal(@Value)^ shr (6+6+5)) and 31; Date.DayOfWeek := 0; - Date.Hour := (Int64Rec(Value).Lo shr (6+6)) and 31; - Date.Minute := (Int64Rec(Value).Lo shr 6) and 63; - Date.Second := Int64Rec(Value).Lo and 63; + Date.Hour := (PCardinal(@Value)^ shr (6+6)) and 31; + Date.Minute := (PCardinal(@Value)^ shr 6) and 63; + Date.Second := PCardinal(@Value)^ and 63; end; procedure TTimeLogBits.From(const S: RawUTF8); @@ -36112,29 +35692,29 @@ procedure TTimeLogBits.From(const S: RawUTF8); procedure TTimeLogBits.From(FileDate: integer); begin {$ifdef MSWINDOWS} - From(LongRec(FileDate).Hi shr 9+1980, - LongRec(FileDate).Hi shr 5 and 15, - LongRec(FileDate).Hi and 31, - LongRec(FileDate).Lo shr 11, - LongRec(FileDate).Lo shr 5 and 63, - LongRec(FileDate).Lo and 31 shl 1); + From(PInt64Rec(@FileDate)^.Hi shr 9+1980, + PInt64Rec(@FileDate)^.Hi shr 5 and 15, + PInt64Rec(@FileDate)^.Hi and 31, + PInt64Rec(@FileDate)^.Lo shr 11, + PInt64Rec(@FileDate)^.Lo shr 5 and 63, + PInt64Rec(@FileDate)^.Lo and 31 shl 1); {$else} // FileDate depends on the running OS From(FileDateToDateTime(FileDate)); {$endif} end; procedure TTimeLogBits.From(DateTime: TDateTime; DateOnly: Boolean=false); -var HH,MM,SS,MS,Y,M,D: word; +var T: TSynSystemTime; V: cardinal; begin + T.FromDate(DateTime); if DateOnly then - HH := 0 else - DecodeTime(DateTime,HH,MM,SS,MS); - DecodeDate(DateTime,Y,M,D); - V := HH+D shl 5+M shl 10+Y shl 14-(1 shl 5+1 shl 10); + T.Hour := 0 else + T.FromTime(DateTime); + V := T.Hour+T.Day shl 5+T.Month shl 10+T.Year shl 14-(1 shl 5+1 shl 10); if DateOnly then Value := Int64(V) shl 12 else - Value := SS+MM shl 6+Int64(V) shl 12; + Value := T.Second+T.Minute shl 6+Int64(V) shl 12; end; procedure TTimeLogBits.FromUnixTime(const UnixTime: TUnixTime); @@ -36209,7 +35789,7 @@ procedure RCU128(var src,dst); procedure RCU(var src,dst; len: integer); begin repeat - MoveFast(src,dst,len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(src,dst,len); ReadBarrier; until CompareMem(@src,@dst,len); end; @@ -36219,7 +35799,8 @@ procedure FromGlobalTime(LocalTime: boolean; out NewTime: TSynSystemTime); newtimesys: TSystemTime absolute NewTime; begin with GlobalTime[LocalTime] do begin - tix := GetTickCount64 {$ifndef MSWINDOWS}shr 3{$endif}; // Linux: 8ms refresh + tix := {$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 + {$ifndef MSWINDOWS}shr 3{$endif}; // Linux: 8ms refresh if clock<>tix then begin // Windows: typically in range of 10-16 ms clock := tix; NewTime.Clear; @@ -36254,7 +35835,7 @@ procedure TTimeLogBits.FromNow; function TTimeLogBits.ToTime: TDateTime; var lo: PtrUInt; begin - lo := {$ifdef CPU64}Value{$else}Int64Rec(Value).Lo{$endif}; + lo := {$ifdef CPU64}Value{$else}PCardinal(@Value)^{$endif}; if lo and (1 shl (6+6+5)-1)=0 then result := 0 else result := EncodeTime((lo shr(6+6))and 31, (lo shr 6)and 63, lo and 63, 0); @@ -36291,7 +35872,7 @@ function TTimeLogBits.ToDate: TDateTime; Y := (lo shr (6+6+5+5+4)) and 4095; {$else} Y := (Value shr (6+6+5+5+4)) and 4095; - lo := Int64Rec(Value).Lo; + lo := PCardinal(@Value)^; {$endif} if (Y=0) or not TryEncodeDate(Y,1+(lo shr(6+6+5+5))and 15,1+(lo shr(6+6+5))and 31,result) then result := 0; @@ -36306,7 +35887,7 @@ function TTimeLogBits.ToDateTime: TDateTime; Y := (lo shr (6+6+5+5+4)) and 4095; {$else} Y := (Value shr (6+6+5+5+4)) and 4095; - lo := Int64Rec(Value).Lo; + lo := PCardinal(@Value)^; {$endif} if (Y=0) or not TryEncodeDate(Y,1+(lo shr(6+6+5+5))and 15,1+(lo shr(6+6+5))and 31,result) then result := 0; @@ -36322,27 +35903,27 @@ function TTimeLogBits.Year: Integer; function TTimeLogBits.Month: Integer; begin - result := 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15; + result := 1+(PCardinal(@Value)^ shr (6+6+5+5)) and 15; end; function TTimeLogBits.Day: Integer; begin - result := 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31; + result := 1+(PCardinal(@Value)^ shr (6+6+5)) and 31; end; function TTimeLogBits.Hour: Integer; begin - result := (Int64Rec(Value).Lo shr (6+6)) and 31; + result := (PCardinal(@Value)^ shr (6+6)) and 31; end; function TTimeLogBits.Minute: Integer; begin - result := (Int64Rec(Value).Lo shr 6) and 63; + result := (PCardinal(@Value)^ shr 6) and 63; end; function TTimeLogBits.Second: Integer; begin - result := Int64Rec(Value).Lo and 63; + result := PCardinal(@Value)^ and 63; end; function TTimeLogBits.ToUnixTime: TUnixTime; @@ -36362,7 +35943,7 @@ function TTimeLogBits.Text(Dest: PUTF8Char; Expanded: boolean; FirstTimeChar: An result := 0; exit; end; - lo := {$ifdef CPU64}Value{$else}Int64Rec(Value).Lo{$endif}; + lo := {$ifdef CPU64}Value{$else}PCardinal(@Value)^{$endif}; if lo and (1 shl (6+6+5)-1)=0 then begin // no Time: just convert date DateToIso8601PChar(Dest, Expanded, @@ -36443,13 +36024,13 @@ function NowUTCToString(Expanded: boolean=true; FirstTimeChar: AnsiChar = ' '): function DateTimeMSToString(DateTime: TDateTime; Expanded: boolean; FirstTimeChar: AnsiChar; const TZD: RawUTF8): RawUTF8; -var HH,MM,SS,MS,Y,M,D: word; +var T: TSynSystemTime; begin // 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format if DateTime=0 then result := '' else begin - DecodeDate(DateTime,Y,M,D); - DecodeTime(DateTime,HH,MM,SS,MS); - result := DateTimeMSToString(HH,MM,SS,MS,Y,M,D,Expanded,FirstTimeChar,TZD); + T.FromDateTime(DateTime); + result := DateTimeMSToString(T.Hour,T.Minute,T.Second,T.MilliSecond, + T.Year,T.Month,T.Day,Expanded,FirstTimeChar,TZD); end; end; @@ -36468,18 +36049,17 @@ function DateTimeMSToString(HH,MM,SS,MS,Y,M,D: cardinal; Expanded: boolean; ('Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'); function DateTimeToHTTPDate(UTCDateTime: TDateTime): RawUTF8; -var HH,MM,SS,MS,Y,M,D: word; +var T: TSynSystemTime; begin if UTCDateTime=0 then begin result := ''; exit; end; - DecodeDate(UTCDateTime,Y,M,D); - DecodeTime(UTCDateTime,HH,MM,SS,MS); + T.FromDateTime(UTCDateTime); FormatUTF8('%, % % % %:%:% GMT', [HTML_WEEK_DAYS[DayOfWeek(UTCDateTime)], - UInt2DigitsToShortFast(D),HTML_MONTH_NAMES[M],UInt4DigitsToShort(Y), - UInt2DigitsToShortFast(HH),UInt2DigitsToShortFast(MM), - UInt2DigitsToShortFast(SS)], result); + UInt2DigitsToShortFast(T.Day),HTML_MONTH_NAMES[T.Month],UInt4DigitsToShort(T.Year), + UInt2DigitsToShortFast(T.Hour),UInt2DigitsToShortFast(T.Minute), + UInt2DigitsToShortFast(T.Second)], result); end; function TimeToString: RawUTF8; @@ -36574,6 +36154,51 @@ procedure TSynSystemTime.FromNowLocal; FromGlobalTime(true,self); end; +procedure TSynSystemTime.FromDateTime(const dt: TDateTime); +begin + FromDate(dt); + FromTime(dt); +end; + +procedure TSynSystemTime.FromDate(const dt: TDateTime); +var t,t2,t3: PtrUInt; +begin + t := Trunc(dt); + t := (t+693900)*4-1; + if PtrInt(t)>=0 then begin + t3 := t div 146097; + t2 := (t-t3*146097) and not 3; + t := PtrUInt(t2+3) div 1461; // PtrUInt() needed for FPC i386 + Year := t3*100+t; + t2 := ((t2+7-t*1461)shr 2)*5; + t3 := PtrUInt(t2-3) div 153; + Day := PtrUInt(t2+2-t3*153) div 5; + if t3<10 then + inc(t3,3) else begin + dec(t3,9); + inc(Year); + end; + Month := t3; + DayOfWeek := 0; + end else + PInt64(@Year)^ := 0; +end; + +procedure TSynSystemTime.FromTime(const dt: TDateTime); +var t,t2: PtrUInt; +begin + t := round(abs(dt)*MSecsPerDay) mod MSecsPerDay; + t2 := t div 3600000; + Hour := t2; + dec(t,t2*3600000); + t2 := t div 60000; + Minute := t2; + dec(t,t2*60000); + t2 := t div 1000; + Second := t2; + MilliSecond := t-t2*1000; +end; + function TSynSystemTime.ToText(Expanded: boolean; FirstTimeChar: AnsiChar; const TZD: RawUTF8): RawUTF8; begin @@ -36584,11 +36209,11 @@ function TSynSystemTime.ToText(Expanded: boolean; procedure TSynSystemTime.AddLogTime(WR: TTextWriter); var y,d100: PtrUInt; P: PUTF8Char; - tab: {$ifdef CPUX86}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; + tab: {$ifdef CPUX86NOTPIC}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; begin - if WR.BEnd-WR.B<=17 then + if WR.BEnd-WR.B<=18 then WR.FlushToStream; - {$ifndef CPUX86}tab := @TwoDigitLookupW;{$endif} + {$ifndef CPUX86NOTPIC}tab := @TwoDigitLookupW;{$endif} y := Year; d100 := y div 100; P := WR.B+1; @@ -36602,10 +36227,83 @@ procedure TSynSystemTime.AddLogTime(WR: TTextWriter); PWord(P+13)^ := tab[Second]; y := Millisecond; PWord(P+15)^ := tab[y shr 4]; - P[17] := ' '; - WR.B := P+16; + inc(WR.B,17); end; +function TSynSystemTime.ToNCSAText(P: PUTF8Char): PtrInt; +var y,d100: PtrUInt; + tab: {$ifdef CPUX86NOTPIC}TWordArray absolute TwoDigitLookupW{$else}PWordArray{$endif}; +begin + {$ifndef CPUX86NOTPIC}tab := @TwoDigitLookupW;{$endif} + PWord(P)^ := tab[Day]; + PCardinal(P+2)^ := PCardinal(@HTML_MONTH_NAMES[Month])^; + P[2] := '/'; // overwrite HTML_MONTH_NAMES[][0] + P[6] := '/'; + y := Year; + d100 := y div 100; + PWord(P+7)^ := tab[d100]; + PWord(P+9)^ := tab[y-(d100*100)]; + P[11] := ':'; + PWord(P+12)^ := tab[Hour]; + P[14] := ':'; + PWord(P+15)^ := tab[Minute]; + P[17] := ':'; + PWord(P+18)^ := tab[Second]; + P[20] := ' '; + result := 21; +end; + +procedure TSynSystemTime.AddNCSAText(WR: TTextWriter); +begin + if WR.BEnd-WR.B<=21 then + WR.FlushToStream; + inc(WR.B,ToNCSAText(WR.B+1)); +end; + +function TSynSystemTime.ToDateTime: TDateTime; +var time: TDateTime; +begin + if TryEncodeDate(Year,Month,Day,result) then + if TryEncodeTime(Hour,Minute,Second,MilliSecond,time) then + result := result+time else + result := 0 else + result := 0; +end; + +procedure TSynSystemTime.IncrementMS(ms: integer); +begin + inc(MilliSecond, ms); + if MilliSecond >= 1000 then + repeat + dec(MilliSecond, 1000); + if Second < 60 then + inc(Second) + else begin + Second := 0; + if Minute < 60 then + inc(Minute) + else begin + Minute := 0; + if Hour < 24 then + inc(Hour) + else begin + Hour := 0; + if Day < MonthDays[false, Month] then + inc(Day) + else begin + Day := 1; + if Month < 12 then + inc(Month) + else begin + Month := 1; + inc(Year); + end; + end; + end; + end; + end; + until MilliSecond < 1000; +end; { TTimeZoneData } @@ -36872,7 +36570,8 @@ function TSynTimeZone.Displays: TStrings; end; -procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName; aMaxSize: Int64); +procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName; + aMaxSize: Int64; aUTCTimeStamp: boolean); var F: THandle; Old: TFileName; Date: array[1..22] of AnsiChar; @@ -36901,7 +36600,9 @@ procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName; aMaxSize: exit; end; PWord(@Date)^ := 13+10 shl 8; // first go to next line - now.FromNowLocal; + if aUTCTimeStamp then + now.FromNowUTC else + now.FromNowLocal; DateToIso8601PChar(@Date[3],true,Now.Year,Now.Month,Now.Day); TimeToIso8601PChar(@Date[13],true,Now.Hour,Now.Minute,Now.Second,0,' '); Date[22] := ' '; @@ -37075,7 +36776,7 @@ function RdRand32: cardinal; {$ifdef CPU64}{$ifdef FPC}nostackframe; assembler; asm{$else} asm .noframe {$endif FPC} {$else} -asm +{$ifdef FPC}nostackframe; assembler;{$endif} asm {$endif} // rdrand eax: same opcodes for x86 and x64 db $0f,$c7,$f0 @@ -37083,34 +36784,33 @@ function RdRand32: cardinal; end; {$endif CPUINTEL} -type - {$ifdef UNICODE}TLecuyer = record{$else}TLecuyer = object{$endif} - public - rs1, rs2, rs3: cardinal; - seedcount: cardinal; - procedure Seed(entropy: PByteArray; entropylen: integer); - function Next: cardinal; - end; - threadvar _Lecuyer: TLecuyer; // uses only 16 bytes per thread -procedure TLecuyer.Seed(entropy: PByteArray; entropylen: integer); +procedure TLecuyer.Seed(entropy: PByteArray; entropylen: PtrInt); var time, crc: THash128Rec; - i: integer; + i, j: PtrInt; begin repeat QueryPerformanceCounter(time.Lo); - time.i2 := UnixTimeUTC; - time.i3 := integer(GetCurrentThreadID); + time.Hi := UnixMSTimeUTC xor Int64(GetCurrentThreadID); crcblock(@crc.b,@time.b); crcblock(@crc.b,@ExeVersion.Hash.b); if entropy<>nil then - for i := 0 to entropylen-1 do - crc.b[i and 15] := crc.b[i and 15] xor entropy^[i]; + for i := 0 to entropylen-1 do begin + j := i and 15; + crc.b[j] := crc.b[j] xor entropy^[i]; + end; rs1 := rs1 xor crc.c0; rs2 := rs2 xor crc.c1; rs3 := rs3 xor crc.c2; + {$ifdef CPUINTEL} + if cfRAND in CpuFeatures then begin // won't hurt e.g. from Random32gsl + rs1 := rs1 xor RdRand32; + rs2 := rs2 xor RdRand32; + rs3 := rs3 xor RdRand32; + end; + {$endif CPUINTEL} until (rs1>1) and (rs2>7) and (rs3>15); seedcount := 1; for i := 1 to crc.i3 and 15 do @@ -37131,6 +36831,11 @@ function TLecuyer.Next: cardinal; result := rs1 xor rs2 xor result; end; +function TLecuyer.Next(max: cardinal): cardinal; +begin + result := (QWord(Next)*max)shr 32; +end; + procedure Random32Seed(entropy: pointer; entropylen: integer); begin _Lecuyer.Seed(entropy,entropylen); @@ -37161,9 +36866,9 @@ function Random32gsl(max: cardinal): cardinal; end; procedure FillRandom(Dest: PCardinalArray; CardinalCount: integer; forcegsl: boolean); -var i: integer; +var i: PtrInt; c: cardinal; - timenow: Int64; + seed: TQWordRec; lecuyer: ^TLecuyer; begin {$ifdef CPUINTEL} @@ -37171,12 +36876,12 @@ procedure FillRandom(Dest: PCardinalArray; CardinalCount: integer; forcegsl: boo lecuyer := nil else {$endif} lecuyer := @_Lecuyer; - QueryPerformanceCounter(timenow); - c := crc32c(ExeVersion.Hash.c3,@timenow,SizeOf(timenow)); + QueryPerformanceCounter(PInt64(@seed)^); + c := crc32cBy4(seed.L,seed.H); {$ifdef CPUINTEL} if lecuyer=nil then for i := 0 to CardinalCount-1 do begin - c := c xor RdRand32 xor crc32ctab[0,(c+cardinal(i)) and 1023]; + c := crc32cBy4(c,RdRand32); // won't trust plain Intel values Dest^[i] := Dest^[i] xor c; end else {$endif} @@ -37229,6 +36934,7 @@ function StringToGUID(const text: string): TGUID; function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar; var c: QWord; + d: cardinal; {$ifndef CPU64}c64: Int64Rec absolute c;{$endif} begin if Value=0 then begin @@ -37245,7 +36951,8 @@ function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar; YearToPChar(c,PUTF8Char(P)-4); end else begin result := StrUInt64(P-1,c); - PCardinal(P-4)^ := PCardinal(P-5)^; + d := PCardinal(P-5)^; // in two explit steps for CPUARM (alf) + PCardinal(P-4)^ := d; P[-5] := '.'; // insert '.' just before last 4 decimals end; if Value<0 then begin @@ -37298,7 +37005,7 @@ function Curr64ToPChar(const Value: Int64; Dest: PUTF8Char): PtrInt; if Decim and $ffff0000=ord('0')shl 16+ord('0')shl 24 then dec(result,2); // 2 decimals end; - MoveFast(P^,Dest^,result); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,Dest^,result); end; function StrToCurr64(P: PUTF8Char; NoDecimal: PBoolean=nil): Int64; @@ -37326,7 +37033,7 @@ function StrToCurr64(P: PUTF8Char; NoDecimal: PBoolean=nil): Int64; c := byte(P^)-48; if c>9 then exit; - Int64Rec(result).Lo := c; + PCardinal(@result)^ := c; inc(P); repeat if P^<>'.' then begin @@ -37422,6 +37129,11 @@ function TrimLeftLowerCase(const V: RawUTF8): PUTF8Char; end; function TrimLeftLowerCaseToShort(V: PShortString): ShortString; +begin + TrimLeftLowerCaseToShort(V,result); +end; + +procedure TrimLeftLowerCaseToShort(V: PShortString; out result: ShortString); var P: PAnsiChar; L: integer; begin @@ -37495,60 +37207,63 @@ function UnCamelCase(D, P: PUTF8Char): integer; Number: boolean; label Next; begin - Space := D; DBeg := D; - SpaceBeg := D; - if (D<>nil) and (P<>nil) then // avoid GPF - repeat - CapitalCount := 0; - Number := P^ in ['0'..'9']; - if Number then - repeat - inc(CapitalCount); - D^ := P^; - inc(P); + if (D<>nil) and (P<>nil) then begin // avoid GPF + Space := D; + SpaceBeg := D; + repeat + CapitalCount := 0; + Number := P^ in ['0'..'9']; + if Number then + repeat + inc(CapitalCount); + D^ := P^; + inc(P); + inc(D); + until not (P^ in ['0'..'9']) else + repeat + inc(CapitalCount); + D^ := P^; + inc(P); + inc(D); + until not (P^ in ['A'..'Z']); + if P^=#0 then break; // no lowercase conversion of last fully uppercased word + if (CapitalCount > 1) and not Number then begin + dec(P); + dec(D); + end; + while P^ in ['a'..'z'] do begin + D^ := P^; inc(D); - until not (P^ in ['0'..'9']) else - repeat - inc(CapitalCount); - D^ := P^; + inc(P); + end; + if P^='_' then + if P[1]='_' then begin + D^ := ':'; inc(P); inc(D); - until not (P^ in ['A'..'Z']); - if P^=#0 then break; // no lowercase conversion of last fully uppercased word - if (CapitalCount > 1) and not Number then begin - dec(P); - dec(D); - end; - while P^ in ['a'..'z'] do begin - D^ := P^; + goto Next; + end else begin + PWord(D)^ := ord(' ')+ord('-')shl 8; + inc(D,2); + Next: if Space=SpaceBeg then + SpaceBeg := D+1; + inc(P); + Space := D+1; + end else + Space := D; + if P^=#0 then break; + D^ := ' '; inc(D); - inc(P); + until false; + if Space>DBeg then + dec(Space); + while Space>SpaceBeg do begin + if Space^ in ['A'..'Z'] then + if not (Space[1] in ['A'..'Z',' ']) then + inc(Space^,32); // lowercase conversion of not last fully uppercased word + dec(Space); end; - if P^='_' then - if P[1]='_' then begin - D^ := ':'; - inc(P); - inc(D); - goto Next; - end else begin - PWord(D)^ := ord(' ')+ord('-')shl 8; - inc(D,2); -Next: if Space=SpaceBeg then - SpaceBeg := D+1; - inc(P); - Space := D+1; - end else - Space := D; - if P^=#0 then break; - D^ := ' '; - inc(D); - until false; - while Space>SpaceBeg do begin - if Space^ in ['A'..'Z'] then - if not (Space[1] in ['A'..'Z',' ']) then - inc(Space^,32); // lowercase conversion of not last fully uppercased word - dec(Space); end; result := D-DBeg; end; @@ -37563,7 +37278,7 @@ procedure CamelCase(P: PAnsiChar; len: integer; var s: RawUTF8; len := SizeOf(tmp); for i := 0 to len - 1 do if not (ord(P[i]) in isWord) then begin - MoveFast(P^,tmp,i); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,tmp,i); inc(P,i); d := @tmp[i]; dec(len,i); @@ -38092,7 +37807,7 @@ function MultiPartFormDataEncode(const MultiPart: TMultiPartDynArray; with MultiPart[i] do begin if FileName='' then W.Add('--%'#13#10'Content-Disposition: form-data; name="%"'#13#10+ - 'Content-Type: %'#13#10#13#10'%'#10'--%'#13#10, + 'Content-Type: %'#13#10#13#10'%'#13#10'--%'#13#10, [bound,Name,ContentType,Content,bound]) else begin // if this is the first file, create the header for files if filescount=0 then begin @@ -38265,7 +37980,7 @@ function AddSortedRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer end; n := Length(Values); if ValuesCount=n then begin - inc(n,256+n shr 3); + n := NextGrow(n); SetLength(Values,n); if CoValues<>nil then SetLength(CoValues^,n); @@ -38273,11 +37988,11 @@ function AddSortedRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer n := ValuesCount; if resultnil then begin - {$ifdef CPU64}n := n shr 1;{$endif} // 64 bit pointer size is twice an integer - MoveFast(CoValues^[result],CoValues^[result+1],n); + {$ifdef CPU64}n := n shr 1;{$endif} // 64-bit pointer size is twice an integer + {$ifdef FPC}Move{$else}MoveFast{$endif}(CoValues^[result],CoValues^[result+1],n); end; end else result := n; @@ -38288,7 +38003,7 @@ function AddSortedRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer type /// used internaly for faster quick sort - {$ifdef UNICODE}TQuickSortRawUTF8 = record{$else}TQuickSortRawUTF8 = object{$endif} + {$ifdef FPC_OR_UNICODE}TQuickSortRawUTF8 = record{$else}TQuickSortRawUTF8 = object{$endif} public Values: PPointerArray; Compare: TUTF8Compare; @@ -38323,10 +38038,16 @@ procedure TQuickSortRawUTF8.Sort(L, R: PtrInt); Inc(I); Dec(J); end; until I > J; - if L < J then - Sort(L, J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + Sort(L, J); + L := I; + end else begin + if I < R then + Sort(I, R); + R := J; + end; + until L >= R; end; procedure QuickSortRawUTF8(var Values: TRawUTF8DynArray; ValuesCount: integer; @@ -38352,7 +38073,8 @@ function DeleteRawUTF8(var Values: TRawUTF8DynArray; Index: integer): boolean; dec(n); Values[Index] := ''; // avoid GPF if n>Index then begin - MoveFast(pointer(Values[Index+1]),pointer(Values[Index]),(n-Index)*SizeOf(pointer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + pointer(Values[Index+1]),pointer(Values[Index]),(n-Index)*SizeOf(pointer)); PtrUInt(Values[n]) := 0; // avoid GPF end; SetLength(Values,n); @@ -38373,8 +38095,10 @@ function DeleteRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer; dec(n,Index); if n>0 then begin if CoValues<>nil then - MoveFast(CoValues^[Index+1],CoValues^[Index],n*SizeOf(Integer)); - MoveFast(pointer(Values[Index+1]),pointer(Values[Index]),n*SizeOf(pointer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + CoValues^[Index+1],CoValues^[Index],n*SizeOf(Integer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + pointer(Values[Index+1]),pointer(Values[Index]),n*SizeOf(pointer)); PtrUInt(Values[ValuesCount]) := 0; // avoid GPF end; result := true; @@ -38399,21 +38123,6 @@ function ToText(const aIntelCPUFeatures: TIntelCpuFeatures; const Sep: RawUTF8): end; end; -function SystemInfoJson: RawUTF8; -var cpu,mem: RawUTF8; -begin - cpu := TSystemUse.Current(false).HistoryText(0,15,@mem); - with SystemInfo do - result := JSONEncode([ - 'host',ExeVersion.Host,'user',ExeVersion.User,'os',OSVersionText, - 'cpu',CpuInfoText,'bios',BiosInfoText, - {$ifdef MSWINDOWS}{$ifndef CPU64}'wow64',IsWow64,{$endif}{$endif MSWINDOWS} - {$ifdef CPUINTEL}'cpufeatures', LowerCase(ToText(CpuFeatures, ' ')),{$endif} - 'processcpu',cpu,'processmem',mem, - 'freemem',TSynMonitorMemory.FreeAsText, - 'freedisk',TSynMonitorDisk.FreeAsText]); -end; - {$ifdef MSWINDOWS} // wrapper around some low-level Windows-specific API @@ -38445,47 +38154,6 @@ function GetFileVersion(const FileName: TFileName): cardinal; end; {$endif DELPHI6OROLDER} -function EnumAllProcesses(out Count: Cardinal): TCardinalDynArray; -var n: cardinal; -begin - n := 2048; - repeat - SetLength(result, n); - if EnumProcesses(pointer(result), n * 4, Count) then - Count := Count shr 2 else - Count := 0; - if Count < n then begin - if Count = 0 then - result := nil; - exit; - end; - inc(n, 1024); // (very unlikely) too small buffer - until n>8192; -end; - -function EnumProcessName(PID: Cardinal): RawUTF8; -var h: THandle; - len: DWORD; - name: array[0..4095] of WideChar; -begin - result := ''; - if PID = 0 then - exit; - h := OpenProcess(OpenProcessAccess, false, PID); - if h <> 0 then - try - if Assigned(QueryFullProcessImageNameW) then begin - len := high(name); - if QueryFullProcessImageNameW(h, 0, name, @len) then - RawUnicodeToUtf8(name, len, result); - end else - if GetModuleFileNameExW(h,0,name,high(name))<>0 then - RawUnicodeToUtf8(name, StrLenW(name), result); - finally - CloseHandle(h); - end; -end; - function WndProcMethod(Hwnd: HWND; Msg,wParam,lParam: integer): integer; stdcall; var obj: TObject; dsp: TMessage; @@ -38512,7 +38180,7 @@ function CreateInternalWindow(const aWindowName: string; aObject: TObject): HWND result := 0; if GetClassInfo(HInstance, pointer(aWindowName), TempClass) then exit; // class name already registered -> fail - FillCharFast(TempClass,SizeOf(TempClass),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(TempClass,SizeOf(TempClass),0); TempClass.hInstance := HInstance; TempClass.lpfnWndProc := @DefWindowProc; TempClass.lpszClassName := pointer(aWindowName); @@ -38576,13 +38244,6 @@ function SetAppUserModelID(const AppUserModelID: string): boolean; LastAppUserModelID := AppUserModelID; end; -{$else} - -// wrapper around some low-level OS (non Windows) specific API - -const - _SC_PAGE_SIZE = $1000; - {$endif MSWINDOWS} { TFileVersion } @@ -38747,6 +38408,18 @@ function TFileVersion.VersionInfo: RawUTF8; FormatUTF8('% % %',[ExtractFileName(fFileName),fDetailed,BuildDateTimeString],result); end; +function TFileVersion.UserAgent: RawUTF8; +begin + if self=nil then + result := '' else + FormatUTF8('%/%%',[GetFileNameWithoutExt(ExtractFileName(fFileName)), + DetailedOrVoid,OS_INITIAL[OS_KIND]],result); + {$ifdef MSWINDOWS} + if OSVersion in WINDOWS_32 then + result := result+'32'; + {$endif} +end; + class function TFileVersion.GetVersionInfo(const aFileName: TFileName): RawUTF8; begin with Create(aFileName,0,0,0,0) do @@ -38847,17 +38520,19 @@ function GetSystemPath(kind: TSystemPath): TFileName; CSIDL_COMMON_APPDATA = $0023; CSIDL_COMMON_DOCUMENTS = $002E; CSIDL: array[TSystemPath] of integer = ( - // spCommonData, spUserData, spCommonDocuments + // spCommonData, spUserData, spCommonDocuments CSIDL_COMMON_APPDATA, CSIDL_LOCAL_APPDATA, CSIDL_COMMON_DOCUMENTS, // spUserDocuments, spTempFolder, spLog - CSIDL_PERSONAL, 0, CSIDL_COMMON_APPDATA); + CSIDL_PERSONAL, 0, CSIDL_LOCAL_APPDATA); ENV: array[TSystemPath] of TFileName = ( - 'ALLUSERSAPPDATA', 'LOCALAPPDATA', '', '', 'TEMP', 'ALLUSERSAPPDATA'); + 'ALLUSERSAPPDATA', 'LOCALAPPDATA', '', '', 'TEMP', 'LOCALAPPDATA'); var tmp: array[0..MAX_PATH] of char; k: TSystemPath; begin - if _SystemPath[spCommonData]='' then begin + if _SystemPath[spCommonData]='' then for k := low(k) to high(k) do + if (k=spLog) and IsDirectoryWritable(ExeVersion.ProgramFilePath) then + _SystemPath[k] := EnsureDirectoryExists(ExeVersion.ProgramFilePath+'log') else if (CSIDL[k]<>0) and (SHGetFolderPath(0,CSIDL[k],0,0,@tmp)=S_OK) then _SystemPath[k] := IncludeTrailingPathDelimiter(tmp) else begin _SystemPath[k] := GetEnvironmentVariable(ENV[k]); @@ -38865,33 +38540,42 @@ function GetSystemPath(kind: TSystemPath): TFileName; _SystemPath[k] := GetEnvironmentVariable('APPDATA'); _SystemPath[k] := IncludeTrailingPathDelimiter(_SystemPath[k]); end; - _SystemPath[spTempFolder] := IncludeTrailingPathDelimiter(GetEnvironmentVariable('TEMP')); - end; result := _SystemPath[kind]; end; {$else MSWINDOWS} var - _HomePath, _TempPath, _LogPath: TFileName; + _HomePath, _TempPath, _UserPath, _LogPath: TFileName; function GetSystemPath(kind: TSystemPath): TFileName; begin case kind of spLog: begin if _LogPath='' then - if DirectoryExists('/var/log') then - _LogPath := '/var/log/' else - _LogPath := GetSystemPath(spUserDocuments); // fallback to HOME + if IsDirectoryWritable('/var/log') then + _LogPath := '/var/log/' else // may not be writable by not root on POSIX + if IsDirectoryWritable(ExeVersion.ProgramFilePath) then + _LogPath := ExeVersion.ProgramFilePath else + _LogPath := GetSystemPath(spUserData); result := _LogPath; end; + spUserData: begin + if _UserPath='' then begin // ~/.cache/appname + _UserPath := GetEnvironmentVariable('XDG_CACHE_HOME'); + if (_UserPath='') or not IsDirectoryWritable(_UserPath) then + _UserPath := EnsureDirectoryExists(GetSystemPath(spUserDocuments)+'.cache'); + _UserPath := EnsureDirectoryExists(_UserPath+UTF8ToString(ExeVersion.ProgramName)); + end; + result := _UserPath; + end; spTempFolder: begin if _TempPath='' then begin _TempPath := GetEnvironmentVariable('TMPDIR'); // POSIX if _TempPath='' then _TempPath := GetEnvironmentVariable('TMP'); if _TempPath='' then - if DirectoryExists('/var/tmp') then - _TempPath := '/var/tmp' else - _TempPath := '/tmp'; + if DirectoryExists('/tmp') then + _TempPath := '/tmp' else + _TempPath := '/var/tmp'; _TempPath := IncludeTrailingPathDelimiter(_TempPath); end; result := _TempPath; @@ -38941,10 +38625,10 @@ procedure PatchCode(Old,New: pointer; Size: integer; Backup: pointer=nil; if Backup<>nil then for i := 0 to Size-1 do // do not use Move() here PByteArray(Backup)^[i] := PByteArray(Old)^[i]; - PageSize := _SC_PAGE_SIZE; - AlignedAddr := PtrUInt(Old) and not (PageSize - 1); - while PtrUInt(Old) + PtrUInt(Size) >= AlignedAddr + PageSize do - Inc(PageSize,_SC_PAGE_SIZE); + PageSize := SystemInfo.dwPageSize; + AlignedAddr := PtrUInt(Old) and not (PageSize-1); + while PtrUInt(Old)+PtrUInt(Size)>=AlignedAddr+PageSize do + Inc(PageSize,SystemInfo.dwPageSize); {$ifdef USEMPROTECT} if mprotect(Pointer(AlignedAddr),PageSize,PROT_READ or PROT_WRITE or PROT_EXEC)=0 then {$else} @@ -38969,7 +38653,7 @@ procedure PatchCodePtrUInt(Code: PPtrUInt; Value: PtrUInt; procedure RedirectCode(Func, RedirectFunc: Pointer; Backup: PPatchCode=nil); var NewJump: packed record Code: byte; // $e9 = jmp {relative} - Distance: integer; // relative jump is 32 bit even on CPU64 + Distance: integer; // relative jump is 32-bit even on CPU64 end; begin if (Func=nil) or (RedirectFunc=nil) then @@ -39091,7 +38775,8 @@ function TSortedWordArray.Add(aValue: Word): PtrInt; if Count=length(Values) then SetLength(Values,Count+100); if result J; - if L < J then - QuickSortCompare(OnCompare,Index,L,J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortCompare(OnCompare, Index, L, J); + L := I; + end else begin + if I < R then + QuickSortCompare(OnCompare, Index, I, R); + R := J; + end; + until L >= R; end; procedure Exchg32(var A,B: integer); {$ifdef HASINLINE}inline;{$endif} @@ -39436,14 +39127,52 @@ function FromVarUInt32High(var Source: PByte): cardinal; result := result and $FFFFFFF or c; end; +function ToVarInt64(Value: Int64; Dest: PByte): PByte; +begin // 0=0,1=1,2=-1,3=2,4=-2... + {$ifdef CPU32} + if Value<=0 then + // 0->0, -1->2, -2->4.. + result := ToVarUInt64((-Value) shl 1,Dest) else + // 1->1, 2->3.. + result := ToVarUInt64((Value shl 1)-1,Dest); + {$else} + if Value<=0 then + // 0->0, -1->2, -2->4.. + Value := (-Value) shl 1 else + // 1->1, 2->3.. + Value := (Value shl 1)-1; + result := ToVarUInt64(Value,Dest); + {$endif} +end; + function ToVarUInt64(Value: QWord; Dest: PByte): PByte; +label _1,_2,_3; // ugly but fast var c: cardinal; begin - if {$ifdef CPU32}Int64Rec(Value).Hi=0{$else}Value shr 32=0{$endif} then begin - result := ToVarUInt32(Value,Dest); + c := Value; + if {$ifdef CPU32}PInt64Rec(@Value)^.Hi=0{$else}Value shr 32=0{$endif} then begin + if c>$7f then begin // inlined result := ToVarUInt32(Value,Dest); + if c<$80 shl 7 then goto _1 else + if c<$80 shl 14 then goto _2 else + if c<$80 shl 21 then goto _3; + Dest^ := (c and $7F) or $80; + c := c shr 7; + inc(Dest); + _3: Dest^ := (c and $7F) or $80; + c := c shr 7; + inc(Dest); + _2: Dest^ := (c and $7F) or $80; + c := c shr 7; + inc(Dest); + _1: Dest^ := (c and $7F) or $80; + c := c shr 7; + inc(Dest); + end; + Dest^ := c; + inc(Dest); + result := Dest; exit; end; - c := Value; PCardinal(Dest)^ := (c and $7F) or (((c shr 7)and $7F)shl 8) or (((c shr 14)and $7F)shl 16) or (((c shr 21)and $7F)shl 24) or $80808080; Value := Value shr 28; @@ -39488,24 +39217,6 @@ function FromVarUInt64(var Source: PByte): QWord; Source := p; end; -function ToVarInt64(Value: Int64; Dest: PByte): PByte; -begin // 0=0,1=1,2=-1,3=2,4=-2... - {$ifdef CPU32} - if Value<=0 then - // 0->0, -1->2, -2->4.. - result := ToVarUInt64((-Value) shl 1,Dest) else - // 1->1, 2->3.. - result := ToVarUInt64((Value shl 1)-1,Dest); - {$else} - if Value<=0 then - // 0->0, -1->2, -2->4.. - Value := (-Value) shl 1 else - // 1->1, 2->3.. - Value := (Value shl 1)-1; - result := ToVarUInt64(Value,Dest); - {$endif} -end; - function FromVarInt64(var Source: PByte): Int64; var c,n: PtrUInt; begin // 0=0,1=1,2=-1,3=2,4=-2... @@ -39545,7 +39256,7 @@ function FromVarInt64(var Source: PByte): Int64; inc(Source); until false; result := result or (Int64(c) shl n); - if Int64Rec(result).Lo and 1<>0 then + if PCardinal(@result)^ and 1<>0 then // 1->1, 3->2.. result := result shr 1+1 else // 0->0, 2->-1, 4->-2.. @@ -39585,7 +39296,7 @@ function FromVarInt64Value(Source: PByte): Int64; inc(Source); until false; result := result or (Int64(c) shl n); - if {$ifdef CPU64}result{$else}Int64Rec(result).Lo{$endif} and 1<>0 then + if {$ifdef CPU64}result{$else}PCardinal(@result)^{$endif} and 1<>0 then // 1->1, 3->2.. result := result shr 1+1 else // 0->0, 2->-1, 4->-2.. @@ -39604,7 +39315,10 @@ function FromVarInt64Value(Source: PByte): Int64; function GotoNextVarInt(Source: PByte): pointer; begin if Source<>nil then begin - while Source^>$7f do inc(Source); + if Source^>$7f then + repeat + inc(Source) + until Source^<=$7f; inc(Source); end; result := Source; @@ -39616,7 +39330,7 @@ function ToVarString(const Value: RawUTF8; Dest: PByte): PByte; Len := Length(Value); Dest := ToVarUInt32(Len,Dest); if Len>0 then begin - MoveFast(pointer(Value)^,Dest^,Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Value)^,Dest^,Len); result := pointer(PAnsiChar(Dest)+Len); end else result := Dest; @@ -39697,7 +39411,7 @@ function OldRTTIManagedSize(typeInfo: Pointer): SizeInt; inline; procedure RecordCopy(var Dest; const Source; TypeInfo: pointer); begin // external name 'FPC_COPY' does not work as we need FPCFinalize(@Dest,TypeInfo); - MoveFast(Source,Dest,OldRTTIManagedSize(TypeInfo)); + Move(Source,Dest,OldRTTIManagedSize(TypeInfo)); FPCRecordAddRef(Dest,TypeInfo); end; {$else} @@ -39935,7 +39649,7 @@ function ManagedTypeSaveLength(data: PAnsiChar; info: PTypeInfo; result := DynArray.SaveToLength; end; tkInterface: begin - len := SizeOf(Int64); // consume 64 bits even on CPU32 + len := SizeOf(Int64); // consume 64-bit even on CPU32 result := SizeOf(PtrUInt); end; else @@ -39965,7 +39679,7 @@ function ManagedTypeSave(data, dest: PAnsiChar; info: PTypeInfo; itemsize := itemsize*2; {$endif} result := pointer(ToVarUInt32(itemsize,pointer(dest))); - MoveFast(pointer(P^)^,result^,itemsize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(P^)^,result^,itemsize); inc(result,itemsize); len := SizeOf(PtrUInt); // size of tkLString+tkWString+tkUString in record end; @@ -39976,7 +39690,7 @@ function ManagedTypeSave(data, dest: PAnsiChar; info: PTypeInfo; if info=nil then result := nil else if itemtype=nil then begin - MoveFast(data^,dest^,len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(data^,dest^,len); result := dest+len; end else begin for i := 1 to info^.elCount do begin @@ -40002,7 +39716,7 @@ function ManagedTypeSave(data, dest: PAnsiChar; info: PTypeInfo; {$ifndef DELPHI5OROLDER} tkInterface: begin PIInterface(dest)^ := PIInterface(data)^; // with proper refcount - result := dest+SizeOf(Int64); // consume 64 bits even on CPU32 + result := dest+SizeOf(Int64); // consume 64-bit even on CPU32 len := SizeOf(PtrUInt); end; {$endif} @@ -40050,7 +39764,7 @@ function ManagedTypeLoad(data: PAnsiChar; var source: PAnsiChar; info: PTypeInfo if info=nil then source := nil else if itemtype=nil then begin - MoveFast(source^,data^,result); + {$ifdef FPC}Move{$else}MoveFast{$endif}(source^,data^,result); inc(source,result); end else for i := 1 to info^.elCount do begin @@ -40073,7 +39787,7 @@ function ManagedTypeLoad(data: PAnsiChar; var source: PAnsiChar; info: PTypeInfo {$ifndef DELPHI5OROLDER} tkInterface: begin PIInterface(data)^ := PIInterface(source)^; // with proper refcount - inc(source,SizeOf(Int64)); // consume 64 bits even on CPU32 + inc(source,SizeOf(Int64)); // consume 64-bit even on CPU32 result := SizeOf(PtrUInt); end; {$endif} @@ -40109,7 +39823,7 @@ function RecordEquals(const RecA, RecB; TypeInfo: pointer; A := @RecA; B := @RecB; result := false; - info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(TypeInfo,tkRecordKinds); if info=nil then exit; // raise Exception.CreateUTF8('% is not a record',[Typ^.Name]); if PRecSize<>nil then @@ -40121,7 +39835,7 @@ function RecordEquals(const RecA, RecB; TypeInfo: pointer; offset := 0; for F := 1 to GetManagedFields(info,field) do begin fieldinfo := DeRef(field^.TypeInfo); - {$ifdef FPC_OLDRTTI} // FPC did include RTTI for unmanaged fields + {$ifdef FPC_OLDRTTI} // old FPC did include RTTI for unmanaged fields if not (fieldinfo^.Kind in tkManagedTypes) then begin inc(field); continue; // as with Delphi @@ -40156,7 +39870,7 @@ function RecordSaveLength(const Rec; TypeInfo: pointer; Len: PInteger): integer; R: PAnsiChar; begin R := @Rec; - info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(TypeInfo,tkRecordKinds); if (R=nil) or (info=nil) then begin result := 0; // should have been checked before exit; @@ -40166,7 +39880,7 @@ function RecordSaveLength(const Rec; TypeInfo: pointer; Len: PInteger): integer; Len^ := result; for F := 1 to GetManagedFields(info,field) do begin fieldinfo := DeRef(field^.TypeInfo); - {$ifdef FPC_OLDRTTI} // FPC did include RTTI for unmanaged fields! :) + {$ifdef FPC_OLDRTTI} // old FPC did include RTTI for unmanaged fields! :) if not (fieldinfo^.Kind in tkManagedTypes) then begin inc(field); continue; // as with Delphi @@ -40190,7 +39904,7 @@ function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer; R: PAnsiChar; begin R := @Rec; - info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(TypeInfo,tkRecordKinds); if (R=nil) or (info=nil) then begin result := nil; // should have been checked before exit; @@ -40207,7 +39921,7 @@ function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer; fieldinfo := DeRef(field^.TypeInfo); {$endif} {$endif} - {$ifdef FPC_OLDRTTI} // FPC did include RTTI for unmanaged fields! :) + {$ifdef FPC_OLDRTTI} // old FPC did include RTTI for unmanaged fields! :) if not (fieldinfo^.Kind in tkManagedTypes) then begin inc(field); continue; // as with Delphi @@ -40215,7 +39929,7 @@ function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer; {$endif}; offset := integer(field^.Offset)-offset; if offset>0 then begin - MoveFast(R^,Dest^,offset); + {$ifdef FPC}Move{$else}MoveFast{$endif}(R^,Dest^,offset); inc(R,offset); inc(Dest,offset); end; @@ -40232,7 +39946,7 @@ function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer; if offset<0 then raise ESynException.Create('RecordSave offset<0') else if offset<>0 then begin - MoveFast(R^,Dest^,offset); + {$ifdef FPC}Move{$else}MoveFast{$endif}(R^,Dest^,offset); result := Dest+offset; end else result := Dest; @@ -40245,48 +39959,72 @@ function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer): PAnsiChar; end; function RecordSave(const Rec; TypeInfo: pointer): RawByteString; -var Len: integer; +var destlen,dummylen: integer; + dest: PAnsiChar; begin - Len := RecordSaveLength(Rec,TypeInfo); - SetString(result,nil,Len); - if Len<>0 then - RecordSave(Rec,pointer(result),TypeInfo,Len); + destlen := RecordSaveLength(Rec,TypeInfo); + SetString(result,nil,destlen); + if destlen<>0 then begin + dest := RecordSave(Rec,pointer(result),TypeInfo,dummylen); + if (dest=nil) or (dest-pointer(result)<>destlen) then // paranoid check + raise ESynException.CreateUTF8('RecordSave % len=%<>%', + [TypeInfoToShortString(TypeInfo)^,dest-pointer(result),destlen]); + end; +end; + +function RecordSaveBytes(const Rec; TypeInfo: pointer): TBytes; +var destlen,dummylen: integer; + dest: PAnsiChar; +begin + destlen := RecordSaveLength(Rec,TypeInfo); + result := nil; // don't reallocate TBytes data from a previous call + SetLength(result,destlen); + if destlen<>0 then begin + dest := RecordSave(Rec,pointer(result),TypeInfo,dummylen); + if (dest=nil) or (dest-pointer(result)<>destlen) then // paranoid check + raise ESynException.CreateUTF8('RecordSave % len=%<>%', + [TypeInfoToShortString(TypeInfo)^,dest-pointer(result),destlen]); + end; +end; + +procedure RecordSave(const Rec; var Dest: TSynTempBuffer; TypeInfo: pointer); +var dummy: integer; +begin + Dest.Init(RecordSaveLength(Rec,TypeInfo)); + RecordSave(Rec,Dest.buf,TypeInfo,dummy); end; function RecordSaveBase64(const Rec; TypeInfo: pointer; UriCompatible: boolean): RawUTF8; var len,dummy: integer; - data: RawByteString; - dat: PAnsiChar; + temp: TSynTempBuffer; begin result := ''; len := RecordSaveLength(Rec,TypeInfo); if len=0 then exit; - SetLength(data,len+4); - dat := PAnsiChar(pointer(data))+4; - RecordSave(Rec,dat,TypeInfo,dummy); - PCardinal(data)^ := crc32c(0,dat,len); - result := BinToBase64(data); + temp.Init(len+4); + RecordSave(Rec,PAnsiChar(temp.buf)+4,TypeInfo,dummy); + PCardinal(temp.buf)^ := crc32c(0,PAnsiChar(temp.buf)+4,len); if UriCompatible then - Base64ToURI(result); + result := BinToBase64uri(temp.buf,temp.len) else + result := BinToBase64(temp.buf,temp.len); + temp.Done; end; function RecordLoadBase64(Source: PAnsiChar; Len: integer; var Rec; TypeInfo: pointer; UriCompatible: boolean): boolean; -var data: RawByteString; +var temp: TSynTempBuffer; begin result := false; if Len<=6 then exit; if UriCompatible then - Base64uriToBin(Source,Len,data) else - Base64ToBin(Source,Len,data); - Len := length(data); - if Len<=4 then - exit; - Source := PAnsiChar(pointer(data))+4; - if crc32c(0,Source,Len-4)=PCardinal(data)^ then - result := RecordLoad(Rec,Source,TypeInfo)<>nil; + result := Base64uriToBin(Source,Len,temp) else + result := Base64ToBin(Source,Len,temp); + result := result and (temp.len>=4) and + (crc32c(0,PAnsiChar(temp.buf)+4,temp.len-4)=PCardinal(temp.buf)^) and + (RecordLoad(Rec,PAnsiChar(temp.buf)+4,TypeInfo)<>nil); + temp.Done; end; function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer; @@ -40298,7 +40036,7 @@ function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer; begin result := nil; // indicates error R := @Rec; - info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(TypeInfo,tkRecordKinds); if (R=nil) or (info=nil) then // should have been checked before exit; if Len<>nil then @@ -40322,7 +40060,7 @@ function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer; fieldinfo := DeRef(field^.TypeInfo); {$endif} {$endif} - {$ifdef FPC_OLDRTTI} // FPC did include RTTI for unmanaged fields! :) + {$ifdef FPC_OLDRTTI} // old FPC did include RTTI for unmanaged fields! :) if not (fieldinfo^.Kind in tkManagedTypes) then begin inc(field); continue; // as with Delphi @@ -40330,7 +40068,7 @@ function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer; {$endif}; offset := integer(field^.Offset)-offset; if offset<>0 then begin - MoveFast(Source^,R^,offset); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,R^,offset); inc(Source,offset); inc(R,offset); end; @@ -40345,12 +40083,19 @@ function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer; if offset<0 then raise ESynException.Create('RecordLoad offset<0') else if offset<>0 then begin - MoveFast(Source^,R^,offset); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,R^,offset); result := Source+offset; end else result := Source; end; +function RecordLoad(var Res; const Source: RawByteString; TypeInfo: pointer): boolean; overload; +var P: PAnsiChar; +begin + P := RecordLoad(Res,pointer(Source),TypeInfo,nil); + result := (P<>nil) and (P-pointer(Source)=length(Source)); +end; + {$ifndef FPC} {$ifdef USEPACKAGES} @@ -41374,34 +41119,6 @@ procedure FillCharSSE2; @done: end; -function StrLenSSE42(S: pointer): PtrInt; -asm // warning: may read up to 15 bytes beyond the string itself - mov edx, eax // copy pointer - test eax, eax - jz @null // returns 0 if S=nil - xor eax, eax - pxor xmm0, xmm0 - {$ifdef HASAESNI} - pcmpistri xmm0, dqword[edx], EQUAL_EACH // comparison result in ecx - {$else} - db $66, $0F, $3A, $63, $02, EQUAL_EACH - {$endif} - jnz @loop - mov eax, ecx - ret - nop // for @loop alignment -@loop: add eax, 16 - {$ifdef HASAESNI} - pcmpistri xmm0, dqword[edx + eax], EQUAL_EACH // comparison result in ecx - {$else} - db $66, $0F, $3A, $63, $04, $10, EQUAL_EACH - {$endif} - jnz @loop -@ok: add eax, ecx - ret -@null: db $f3 // rep ret -end; - {$endif DELPHI5OROLDER} {$endif PUREPASCAL} @@ -41417,10 +41134,12 @@ procedure InitRedirectCode; {$else DELPHI5OROLDER} {$ifdef CPU64} {$ifdef HASAESNI} + {$ifdef FORCE_STRSSE42} if cfSSE42 in CpuFeatures then begin StrLen := @StrLenSSE42; StrComp := @StrCompSSE42; end else + {$endif FORCE_STRSSE42} {$endif HASAESNI} StrLen := @StrLenSSE2; {$ifdef WITH_ERMS}{$ifdef MSWINDOWS} // disabled (slower for small blocks) @@ -41434,8 +41153,10 @@ procedure InitRedirectCode; {$else CPU64} {$ifdef CPUINTEL} if cfSSE2 in CpuFeatures then begin + {$ifdef FORCE_STRSSE42} if cfSSE42 in CpuFeatures then StrLen := @StrLenSSE42 else + {$endif FORCE_STRSSE42} StrLen := @StrLenSSE2; FillcharFast := @FillCharSSE2; end else begin @@ -41447,7 +41168,7 @@ procedure InitRedirectCode; MoveFast := @MoveERMSB; FillcharFast := @FillCharERMSB; end else {$endif} - MoveFast := @MoveX87; // SSE2 is not faster than X87 version on 32 bit CPU + MoveFast := @MoveX87; // SSE2 is not faster than X87 version on 32-bit CPU {$endif CPUINTEL} {$endif CPU64} {$endif DELPHI5OROLDER} @@ -41600,7 +41321,7 @@ function TJSONCustomParsers.TryToGetFromRTTI(aDynArrayTypeInfo, RegRoot := TJSONCustomParserRTTI.CreateFromTypeName('',Reg.RecordTypeName); {$ifdef ISDELPHI2010} if RegRoot=nil then begin - info := GetTypeInfo(aRecordTypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(aRecordTypeInfo,tkRecordKinds); if info=nil then exit; // not enough RTTI inc(PByte(info),info^.ManagedCount*SizeOf(TFieldInfo)-SizeOf(TFieldInfo)); @@ -41766,7 +41487,7 @@ function TJSONCustomParsers.Search(aTypeInfo: pointer; begin if (aTypeInfo=nil) or (self=nil) then raise ESynException.CreateUTF8('%.Search(%)',[self,aTypeInfo]); - FillcharFast(Reg,SizeOf(Reg),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Reg,SizeOf(Reg),0); case PTypeKind(aTypeInfo)^ of tkDynArray: begin Reg.DynArrayTypeInfo := aTypeInfo; @@ -41911,12 +41632,12 @@ function ManagedTypeSaveRTTIHash(info: PTypeInfo; var crc: cardinal): integer; {$endif} tkRecord{$ifdef FPC},tkObject{$endif}: // first search from custom RTTI text if not GlobalJSONCustomParsers.RecordRTTITextHash(info,crc,result) then begin - itemtype := GetTypeInfo(info,tkRecordTypeOrSet); + itemtype := GetTypeInfo(info,tkRecordKinds); if itemtype<>nil then begin unmanagedsize := itemtype^.recsize; for i := 1 to GetManagedFields(itemtype,field) do begin info := DeRef(field^.TypeInfo); - {$ifdef FPC_OLDRTTI} // FPC did include RTTI for unmanaged fields + {$ifdef FPC_OLDRTTI} // old FPC did include RTTI for unmanaged fields if info^.Kind in tkManagedTypes then // as with Delphi {$endif} dec(unmanagedsize,ManagedTypeSaveRTTIHash(info,crc)); @@ -41964,6 +41685,7 @@ function RecordLoadJSON(var Rec; JSON: PUTF8Char; TypeInfo: pointer; Reader: TDynArrayJSONCustomReader; FirstChar,EndOfObj: AnsiChar; Val: PUTF8Char; + ValLen: integer; begin // code below must match TTextWriter.AddRecordJSON result := nil; // indicates error if JSON=nil then @@ -41975,10 +41697,10 @@ function RecordLoadJSON(var Rec; JSON: PUTF8Char; TypeInfo: pointer; if not (PTypeKind(TypeInfo)^ in tkRecordTypes) then raise ESynException.CreateUTF8('RecordLoadJSON(%/%)', [PShortString(@PTypeInfo(TypeInfo).NameLen)^,ToText(PTypeKind(TypeInfo)^)^]); - Val := GetJSONField(JSON,JSON,@wasString,@EndOfObj); - if (Val=nil) or not wasString or + Val := GetJSONField(JSON,JSON,@wasString,@EndOfObj,@ValLen); + if (Val=nil) or not wasString or (ValLen<3) or (PInteger(Val)^ and $00ffffff<>JSON_BASE64_MAGIC) or - (RecordLoad(Rec,pointer(Base64ToBin(Val+3)),TypeInfo)=nil) then + (RecordLoad(Rec,pointer(Base64ToBin(PAnsiChar(Val)+3,ValLen-3)),TypeInfo)=nil) then exit; // invalid content end else begin if not GlobalJSONCustomParsers.RecordSearch(TypeInfo,Reader) then @@ -42184,7 +41906,7 @@ function TJSONCustomParserCustomSimple.CustomReader(P: PUTF8Char; [self,fCustomTypeName]); ktSet: begin i32 := GetSetNameValue(fCustomTypeInfo,P,EndOfObject); - MoveFast(i32,aValue,fDataSize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(i32,aValue,fDataSize); result := P; end; else begin // encoded as JSON strings or number @@ -42203,7 +41925,7 @@ function TJSONCustomParserCustomSimple.CustomReader(P: PUTF8Char; i32 := GetCardinal(PropValue); if i32<0 then exit; - MoveFast(i32,aValue,fDataSize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(i32,aValue,fDataSize); result := P; end; ktFixedArray: @@ -42212,14 +41934,14 @@ function TJSONCustomParserCustomSimple.CustomReader(P: PUTF8Char; result := P; ktBinary: if wasString then begin // default hexa serialization - FillcharFast(aValue,fDataSize,0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(aValue,fDataSize,0); if (PropValueLen=0) or ((PropValueLen=fFixedSize*2) and HexDisplayToBin(PAnsiChar(PropValue),@aValue,fFixedSize)) then result := P; end else if fFixedSize<=SizeOf(u64) then begin // allow integer serialization SetQWord(PropValue,u64); - MoveFast(u64,aValue,fDataSize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(u64,aValue,fDataSize); result := P; end; end; @@ -42420,7 +42142,9 @@ class function TJSONCustomParserRTTI.TypeInfoToSimpleRTTIType(Info: pointer; {$endif} end; tkInt64: - {$ifndef FPC}if Info=TypeInfo(QWord) then result := ptQWord else{$endif} + {$ifndef FPC} if Info=TypeInfo(QWord) then result := ptQWord else + {$ifdef UNICODE}with GetTypeInfo(Info)^ do // detect QWord/UInt64 + if MinInt64Value>MaxInt64Value then result := ptQWord else{$endif}{$endif} result := ptInt64; {$ifdef FPC} tkQWord: result := ptQWord; @@ -42505,7 +42229,7 @@ class function TJSONCustomParserRTTI.CreateFromTypeName( end; procedure TJSONCustomParserRTTI.ComputeFullPropertyName; -var i: integer; +var i: PtrInt; begin for i := 0 to high(NestedProperty) do begin NestedProperty[i].ComputeFullPropertyName; @@ -42516,7 +42240,7 @@ procedure TJSONCustomParserRTTI.ComputeFullPropertyName; end; procedure TJSONCustomParserRTTI.ComputeNestedDataSize; -var i: integer; +var i: PtrInt; begin assert(fNestedDataSize=0); fNestedDataSize := 0; @@ -42539,7 +42263,7 @@ procedure TJSONCustomParserRTTI.ComputeDataSizeAfterAdd; SizeOf(TGUID),SizeOf(Int64),SizeOf(TTimeLog), {$ifndef NOVARIANTS}SizeOf(Variant),{$endif} SizeOf(WideString),SizeOf(Word),0); -var i: integer; +var i: PtrInt; begin if fFullPropertyName='' then begin fFullPropertyName := fPropertyName; @@ -42562,7 +42286,7 @@ procedure TJSONCustomParserRTTI.ComputeDataSizeAfterAdd; end; procedure TJSONCustomParserRTTI.FinalizeNestedRecord(var Data: PByte); -var j: integer; +var j: PtrInt; begin for j := 0 to length(NestedProperty)-1 do begin case NestedProperty[j].PropertyType of @@ -42589,7 +42313,7 @@ procedure TJSONCustomParserRTTI.FinalizeNestedRecord(var Data: PByte); procedure TJSONCustomParserRTTI.FinalizeNestedArray(var Data: PtrUInt); var i: integer; - Rec: ^TDynArrayRec; + Rec: PDynArrayRec; ItemData: PByte; begin if Data=0 then @@ -42597,10 +42321,14 @@ procedure TJSONCustomParserRTTI.FinalizeNestedArray(var Data: PtrUInt); ItemData := pointer(Data); Rec := pointer(Data); dec(PtrUInt(Rec),SizeOf(TDynArrayRec)); - for i := 0 to Rec.length-1 do + Data := 0; + if Rec^.refCnt>1 then begin + InterlockedDecrement(PInteger(@Rec^.refCnt)^); // FPC has refCnt: PtrInt + exit; + end; + for i := 1 to Rec.length do FinalizeNestedRecord(ItemData); FreeMem(Rec); - Data := 0; end; procedure TJSONCustomParserRTTI.AllocateNestedArray(var Data: PtrUInt; @@ -42625,7 +42353,8 @@ procedure TJSONCustomParserRTTI.ReAllocateNestedArray(var Data: PtrUInt; ReAllocMem(pointer(Data),SizeOf(TDynArrayRec)+fNestedDataSize*NewLength); OldLength := PDynArrayRec(Data)^.length; if NewLength>OldLength then - FillcharFast(PByteArray(Data)[SizeOf(TDynArrayRec)+fNestedDataSize*OldLength], + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}( + PByteArray(Data)[SizeOf(TDynArrayRec)+fNestedDataSize*OldLength], fNestedDataSize*(NewLength-OldLength),0); PDynArrayRec(Data)^.length := NewLength; inc(Data,SizeOf(TDynArrayRec)); @@ -42681,7 +42410,7 @@ function TJSONCustomParserRTTI.ReadOneLevel(var P: PUTF8Char; var Data: PByte; repeat inc(n); if (ArrayLen<0) and (n>ArrayCapacity) then begin - inc(ArrayCapacity,512+ArrayCapacity shr 3); + ArrayCapacity := NextGrow(ArrayCapacity); Prop.ReAllocateNestedArray(PPtrUInt(Data)^,ArrayCapacity); DynArray := PPointer(Data)^; inc(DynArray,pred(n)*Prop.fNestedDataSize); @@ -42881,7 +42610,7 @@ function Plural(const itemname: shortstring; itemcount: cardinal): shortstring; len := (AppendUInt32ToBuffer(@result[1],itemcount)-PUTF8Char(@result[1]))+1; result[len] := ' '; if ord(itemname[0])<240 then begin // avoid buffer overflow - MoveFast(itemname[1],result[len+1],ord(itemname[0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}(itemname[1],result[len+1],ord(itemname[0])); inc(len,ord(itemname[0])); if itemcount>1 then begin inc(len); @@ -43687,8 +43416,8 @@ function VariantSave(const Value: variant; Dest: PAnsiChar): PAnsiChar; {$endif} end; Dest := pointer(ToVarUInt32(LenBytes,pointer(Dest))); - if LenBytes>0 then begin - MoveFast(PPtrUInt(VAny)^,Dest^,LenBytes); // direct raw copy + if LenBytes>0 then begin // direct raw copy + {$ifdef FPC}Move{$else}MoveFast{$endif}(PPtrUInt(VAny)^,Dest^,LenBytes); inc(Dest,LenBytes); end; end; @@ -44717,6 +44446,16 @@ function _Safe(const DocVariant: variant; ExpectedKind: TDocVariantKind): PDocVa raise EDocVariant.CreateUTF8('_Safe(%)<>%',[ToText(result^.Kind)^,ToText(ExpectedKind)^]); end; +function _CSV(const DocVariantOrString: variant): RawUTF8; +begin + with _Safe(DocVariantOrString)^ do + if dvoIsArray in VOptions then + result := ToCSV else + if (dvoIsObject in VOptions) or (TDocVariantData(DocVariantOrString).VType<=varNull) or + not VariantToUTF8(DocVariantOrString,result) then + result := ''; // VariantToUTF8() returns 'null' for empty/null +end; + function TDocVariantData.GetKind: TDocVariantKind; begin if dvoIsArray in VOptions then @@ -45210,12 +44949,12 @@ function TDocVariantData.InternalAdd(const aName: RawUTF8): integer; include(VOptions,dvoIsArray); end; end; - if VValue=nil then - SetLength(VValue,16) else - if VCount>=length(VValue) then - SetLength(VValue,VCount+VCount shr 3+32); + len := length(VValue); + if VCount>=len then begin + len := NextGrow(VCount); + SetLength(VValue,len); + end; if aName<>'' then begin - len := length(VValue); if Length(VName)<>len then SetLength(VName,len); if dvoInternNames in VOptions then begin // inlined InternNames method @@ -45428,10 +45167,16 @@ procedure QuickSortDocVariant(names: PPointerArray; values: PVariantArray; inc(I); dec(J); end; until I > J; - if L < J then - QuickSortDocVariant(names,values,L,J,Compare); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortDocVariant(names, values, L, J, Compare); + L := I; + end else begin + if I < R then + QuickSortDocVariant(names, values, I, R, Compare); + R := J; + end; + until L >= R; end; procedure TDocVariantData.SortByName(Compare: TUTF8Compare=nil); @@ -45451,7 +45196,7 @@ procedure ExchgValues(v1,v2: PVarData); v1^ := v; end; -procedure ExchgNames(n1,n2: PPointer); +procedure ExchgNames(n1,n2: PPointer); {$ifdef HASINLINE}inline;{$endif} var n: pointer; begin n := n2^; @@ -45482,16 +45227,102 @@ procedure QuickSortDocVariantValues(var Doc: TDocVariantData; inc(I); dec(J); end; until I > J; - if L < J then - QuickSortDocVariantValues(Doc,L,J,Compare); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortDocVariantValues(Doc, L, J, Compare); + L := I; + end else begin + if I < R then + QuickSortDocVariantValues(Doc, I, R, Compare); + R := J; + end; + until L >= R; end; procedure TDocVariantData.SortByValue(Compare: TVariantCompare); begin - if VCount>0 then - QuickSortDocVariantValues(self,0,VCount-1,Compare); + if VCount<=0 then + exit; + if not Assigned(Compare) then + Compare := VariantCompare; + QuickSortDocVariantValues(self,0,VCount-1,Compare); +end; + +type + {$ifdef FPC_OR_UNICODE}TQuickSortDocVariantValuesByField = record + {$else}TQuickSortDocVariantValuesByField = object{$endif} + Lookup: array of PVariant; + Compare: TVariantCompare; + Doc: PDocVariantData; + Reverse: boolean; + procedure Sort(L, R: PtrInt); + end; + +procedure TQuickSortDocVariantValuesByField.Sort(L, R: PtrInt); +var I, J, P: PtrInt; + pivot: PVariant; +begin + if L0 do Dec(J); + end + else begin + while Compare(Lookup[I]^,pivot^)>0 do Inc(I); + while Compare(Lookup[J]^,pivot^)<0 do Dec(J); + end; + if I <= J then begin + if I <> J then begin + if Doc.VName<>nil then + ExchgNames(@Doc.VName[I],@Doc.VName[J]); + ExchgValues(@Doc.VValue[I],@Doc.VValue[J]); + pivot := Lookup[I]; + Lookup[I] := Lookup[J]; + Lookup[J] := pivot; + end; + if P = I then P := J else if P = J then P := I; + inc(I); dec(J); + end; + until I > J; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + Sort(L,J); + L := I; + end else begin + if I < R then + Sort(I,R); + R := J; + end; + until L >= R; +end; + +procedure TDocVariantData.SortArrayByField(const aItemPropName: RawUTF8; + aValueCompare: TVariantCompare; aValueCompareReverse: boolean; aNameSortedCompare: TUTF8Compare); +var + QS: TQuickSortDocVariantValuesByField; + p: pointer; + row: PtrInt; +begin + if (VCount<=0) or (aItemPropName='') or not (dvoIsArray in VOptions) then + exit; + if not Assigned(aValueCompare) then + QS.Compare := VariantCompare else + QS.Compare := aValueCompare; + QS.Reverse := aValueCompareReverse; + SetLength(QS.Lookup,VCount); + for row := 0 to VCount-1 do begin // resolve GetPVariantByName(aIdemPropName) once + p := _Safe(VValue[row])^.GetVarData(aItemPropName,aNameSortedCompare); + if p = nil then + p := @NullVarData; + QS.Lookup[row] := p; + end; + QS.Doc := @self; + QS.Sort(0,VCount-1); end; procedure TDocVariantData.Reverse; @@ -45649,10 +45480,12 @@ function TDocVariantData.Delete(Index: integer): boolean; VarClear(VValue[Index]); if Indexnil then begin - MoveFast(VName[Index+1],VName[Index],(VCount-Index)*SizeOf(pointer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + VName[Index+1],VName[Index],(VCount-Index)*SizeOf(pointer)); PtrUInt(VName[VCount]) := 0; // avoid GPF end; - MoveFast(VValue[Index+1],VValue[Index],(VCount-Index)*SizeOf(variant)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + VValue[Index+1],VValue[Index],(VCount-Index)*SizeOf(variant)); TVarData(VValue[VCount]).VType := varEmpty; // avoid GPF end; result := true; @@ -45813,7 +45646,7 @@ function TDocVariantData.GetAsInteger(const aName: RawUTF8; out aValue: integer; found := GetVarData(aName,aSortedCompare); if found=nil then result := false else - result := VariantToInteger(PVariant(found)^,aValue) + result := VariantToInteger(PVariant(found)^,aValue); end; function TDocVariantData.GetAsInt64(const aName: RawUTF8; out aValue: Int64; @@ -45844,7 +45677,8 @@ function TDocVariantData.GetAsRawUTF8(const aName: RawUTF8; out aValue: RawUTF8; found := GetVarData(aName,aSortedCompare); if found=nil then result := false else begin - VariantToUTF8(PVariant(found)^,aValue,wasString); + if found^.VType>varNull then // default VariantToUTF8(null)='null' + VariantToUTF8(PVariant(found)^,aValue,wasString); result := true; end; end; @@ -47229,7 +47063,7 @@ function SortDynArrayPUTF8CharI(const A,B): integer; function SortDynArrayString(const A,B): integer; begin {$ifdef UNICODE} - result := SysUtils.StrComp(PChar(A),PChar(B)); + result := StrCompW(PWideChar(A),PWideChar(B)); {$else} result := StrComp(PUTF8Char(A),PUTF8Char(B)); {$endif} @@ -47299,6 +47133,16 @@ function SortDynArray512(const A,B): integer; {$ifndef NOVARIANTS} +function VariantCompare(const V1,V2: variant): PtrInt; +begin + result := SortDynArrayVariantComp(TVarData(V1), TVarData(V2), false); +end; + +function VariantCompareI(const V1,V2: variant): PtrInt; +begin + result := SortDynArrayVariantComp(TVarData(V1), TVarData(V2), true); +end; + function SortDynArrayVariantCompareAsString(const A,B: variant): integer; var UA,UB: RawUTF8; wasString: boolean; @@ -47367,6 +47211,8 @@ function SortDynArrayVariantComp(const A,B: TVarData; caseInsensitive: boolean): result := ICMP[VarCompareValue(variant(A),variant(B))] else result := CMP[caseInsensitive](variant(A),variant(B)); end else + if (A.VType<=varNull) or (B.VType<=varNull) then + result := ord(A.VType>varNull)-ord(B.VType>varNull) else if (A.VType and VTYPE_STATIC=0) and (B.VType and VTYPE_STATIC=0) then result := ICMP[VarCompareValue(variant(A),variant(B))] else @@ -47416,14 +47262,14 @@ function TDynArray.GetCount: integer; procedure TDynArray.ElemCopy(const A; var B); begin if ElemType=nil then - MoveFast(A,B,ElemSize) else begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(A,B,ElemSize) else begin {$ifdef FPC} {$ifdef FPC_OLDRTTI} FPCFinalize(@B,ElemType); // inlined CopyArray() - MoveFast(A,B,ElemSize); + Move(A,B,ElemSize); FPCRecordAddRef(B,ElemType); {$else} - FPCRecordCopy(A,B,ElemType); + FPCRecordCopy(A,B,ElemType); // works for any kind of ElemTyp {$endif FPC_OLDRTTI} {$else} CopyArray(@B,@A,ElemType,1); @@ -47440,7 +47286,7 @@ function TDynArray.Add(const Elem): PtrInt; SetCount(result+1); p := PtrUInt(fValue^)+PtrUInt(result)*ElemSize; if ElemType=nil then - MoveFast(Elem,pointer(p)^,ElemSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(Elem,pointer(p)^,ElemSize) else {$ifdef FPC} FPCRecordCopy(Elem,pointer(p)^,ElemType); {$else} @@ -47486,9 +47332,9 @@ procedure TDynArray.Insert(Index: PtrInt; const Elem); SetCount(n+1); if PtrUInt(Index)nil then - FillcharFast(P^,ElemSize,0); // avoid GPF in ElemCopy() below + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[0],P[ElemSize],PtrUInt(n-Index)*ElemSize); + if ElemType<>nil then // avoid GPF in ElemCopy() below + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(P^,ElemSize,0); end else // Index>=Count -> add at the end P := pointer(PtrUInt(fValue^)+PtrUInt(n)*ElemSize); @@ -47522,7 +47368,6 @@ function TDynArray.GetIsObjArray: boolean; procedure TDynArray.Delete(aIndex: PtrInt); var n, len: PtrInt; P: PAnsiChar; - zerolast: boolean; begin if fValue=nil then exit; // avoid GPF if void @@ -47531,21 +47376,16 @@ procedure TDynArray.Delete(aIndex: PtrInt); exit; // out of range dec(n); P := pointer(PtrUInt(fValue^)+PtrUInt(aIndex)*ElemSize); - if ElemType<>nil then begin - {$ifdef FPC}FPCFinalize{$else}_Finalize{$endif}(P,ElemType); - zerolast := true; - end else - if (fIsObjArray=oaTrue) or ((fIsObjArray=oaUnknown) and ComputeIsObjArray) then begin + if ElemType<>nil then + {$ifdef FPC}FPCFinalize{$else}_Finalize{$endif}(P,ElemType) else + if (fIsObjArray=oaTrue) or ((fIsObjArray=oaUnknown) and ComputeIsObjArray) then FreeAndNil(PObject(P)^); - zerolast := true; - end else - zerolast := false; if n>aIndex then begin len := PtrUInt(n-aIndex)*ElemSize; - MoveFast(P[ElemSize],P[0],len); - if zerolast then // avoid GPF - FillcharFast(P[len],ElemSize,0); - end; + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[ElemSize],P[0],len); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(P[len],ElemSize,0); + end else + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(P^,ElemSize,0); SetCount(n); end; @@ -47580,9 +47420,9 @@ procedure TDynArray.ElemCopyAt(index: PtrInt; var Dest); p := ElemPtr(index); if p<>nil then if ElemType=nil then - MoveFast(p^,Dest,ElemSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(p^,Dest,ElemSize) else {$ifdef FPC} - FPCRecordCopy(p^,Dest,ElemType); + FPCRecordCopy(p^,Dest,ElemType); // works for any kind of ElemTyp {$else} CopyArray(@Dest,p,ElemType,1); {$endif} @@ -47595,8 +47435,8 @@ procedure TDynArray.ElemMoveTo(index: PtrInt; var Dest); if (p=nil) or (@Dest=nil) then exit; ElemClear(Dest); - MoveFast(p^,Dest,ElemSize); - FillCharFast(p^,ElemSize,0); // ElemType=nil for ObjArray + {$ifdef FPC}Move{$else}MoveFast{$endif}(p^,Dest,ElemSize); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(p^,ElemSize,0); // ElemType=nil for ObjArray end; procedure TDynArray.ElemCopyFrom(const Source; index: PtrInt; ClearBeforeCopy: boolean); @@ -47605,7 +47445,7 @@ procedure TDynArray.ElemCopyFrom(const Source; index: PtrInt; ClearBeforeCopy: b p := ElemPtr(index); if p<>nil then if ElemType=nil then - MoveFast(Source,p^,ElemSize) else begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source,p^,ElemSize) else begin if ClearBeforeCopy then // safer if Source is a copy of p^ {$ifdef FPC}FPCFinalize{$else}_Finalize{$endif}(p,ElemType); {$ifdef FPC} @@ -47639,7 +47479,7 @@ procedure TDynArray.Reverse; end; end; 4: begin - // optimized version for TIntegerDynArray + TRawUTF8DynArray and such + // optimized version for TIntegerDynArray and such P2 := P1+n*SizeOf(Integer); while P1nil then // may be nil if RecordSave/ManagedTypeSave failed - PCardinal(result-SizeOf(Cardinal))^ := Hash32(result,Dest-result); + PCardinal(result-SizeOf(Cardinal))^ := Hash32(pointer(result),Dest-result); result := Dest; end; @@ -48004,7 +47844,7 @@ function TDynArray.ElemCopyFirstField(Source,Dest: Pointer): boolean; ToKnownType(false); case fKnownType of djBoolean..djDateTimeMS,djHash128..djHash512: // no managed field - MoveFast(Source^,Dest^,fKnownSize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Dest^,fKnownSize); djRawUTF8, djWinAnsi, djRawByteString: PRawByteString(Dest)^ := PRawByteString(Source)^; djSynUnicode: @@ -48028,7 +47868,7 @@ function TDynArray.LoadKnownType(Data,Source: PAnsiChar): boolean; if fKnownType=djNone then ToKnownType({exacttype=}false); // set fKnownType and fKnownSize if fKnownType in [djBoolean..djDateTimeMS] then begin - MoveFast(Source^,Data^,fKnownSize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Data^,fKnownSize); result := true; end else begin info := KINDTYPE_INFO[fKnownType]; @@ -48258,7 +48098,7 @@ function TDynArrayLoadFrom.Step(out Elem): boolean; result := false; if (Position<>nil) and (CurrentJ then + {$ifndef PUREPASCAL} // inlined Exchg() is just fine if ElemSize=SizeOf(pointer) then begin // optimized version e.g. for TRawUTF8DynArray/TObjectDynArray tmp := PPointer(IP)^; PPointer(IP)^ := PPointer(JP)^; PPointer(JP)^ := tmp; end else + {$endif} // generic exchange of row element data Exchg(IP,JP,ElemSize); if P = I then P := J else @@ -48628,10 +48473,94 @@ procedure TDynArrayQuickSort.QuickSort(L, R: PtrInt); Inc(I); Dec(J); end; until I > J; - if L < J then - QuickSort(L, J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSort(L, J); + L := I; + end else begin + if I < R then + QuickSort(I, R); + R := J; + end; + until L >= R; +end; + +procedure TDynArrayQuickSort.QuickSortEvent(L, R: PtrInt); +var I, J: PtrInt; +begin + if L0 do begin + dec(J); + dec(JP,ElemSize); + end; + if I <= J then begin + if I<>J then + Exchg(IP,JP,ElemSize); + if P = I then P := J else + if P = J then P := I; + Inc(I); Dec(J); + end; + until I > J; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortEvent(L, J); + L := I; + end else begin + if I < R then + QuickSortEvent(I, R); + R := J; + end; + until L >= R; +end; + +procedure TDynArrayQuickSort.QuickSortEventReverse(L, R: PtrInt); +var I, J: PtrInt; +begin + if L0 do begin + inc(I); + inc(IP,ElemSize); + end; + while CompareEvent(JP^,Pivot^)<0 do begin + dec(J); + dec(JP,ElemSize); + end; + if I <= J then begin + if I<>J then + Exchg(IP,JP,ElemSize); + if P = I then P := J else + if P = J then P := I; + Inc(I); Dec(J); + end; + until I > J; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortEventReverse(L, J); + L := I; + end else begin + if I < R then + QuickSortEventReverse(I, R); + R := J; + end; + until L >= R; end; procedure TDynArrayQuickSort.QuickSortIndexed(L, R: PtrInt); @@ -48657,15 +48586,56 @@ procedure TDynArrayQuickSort.QuickSortIndexed(L, R: PtrInt); Inc(I); Dec(J); end; until I > J; - if L < J then - QuickSortIndexed(L, J); - L := I; - until I >= R; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortIndexed(L, J); + L := I; + end else begin + if I < R then + QuickSortIndexed(I, R); + R := J; + end; + until L >= R; end; procedure TDynArray.Sort(aCompare: TDynArraySortCompare); begin - SortRange(0,Count-1,aCompare) + SortRange(0,Count-1,aCompare); + fSorted := true; +end; + +procedure QuickSortPtr(L, R: PtrInt; Compare: TDynArraySortCompare; V: PPointerArray); +var I, J, P: PtrInt; + tmp: pointer; +begin + if L0 do + dec(J); + if I <= J then begin + tmp := V[I]; + V[I] := V[J]; + V[J] := tmp; + if P = I then P := J else + if P = J then P := I; + Inc(I); Dec(J); + end; + until I > J; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + QuickSortPtr(L, J, Compare, V); + L := I; + end else begin + if I < R then + QuickSortPtr(I, R, Compare, V); + R := J; + end; + until L >= R; end; procedure TDynArray.SortRange(aStart, aStop: integer; aCompare: TDynArraySortCompare); @@ -48676,12 +48646,28 @@ procedure TDynArray.SortRange(aStart, aStop: integer; aCompare: TDynArraySortCom if @aCompare=nil then Quicksort.Compare := @fCompare else Quicksort.Compare := aCompare; - if (@Quicksort.Compare<>nil) and (fValue<>nil) and (fValue^<>nil) then begin - Quicksort.Value := fValue^; - Quicksort.ElemSize := ElemSize; - Quicksort.QuickSort(aStart,aStop); - fSorted := true; - end; + if (@Quicksort.Compare<>nil) and (fValue<>nil) and (fValue^<>nil) then + if ElemSize=SizeOf(pointer) then + QuickSortPtr(aStart,aStop,QuickSort.Compare,fValue^) else begin + Quicksort.Value := fValue^; + Quicksort.ElemSize := ElemSize; + Quicksort.QuickSort(aStart,aStop); + end; +end; + +procedure TDynArray.Sort(const aCompare: TEventDynArraySortCompare; aReverse: boolean); +var QuickSort: TDynArrayQuickSort; + R: PtrInt; +begin + if not Assigned(aCompare) or (fValue = nil) or (fValue^=nil) then + exit; // nothing to sort + Quicksort.CompareEvent := aCompare; + Quicksort.Value := fValue^; + Quicksort.ElemSize := ElemSize; + R := Count-1; + if aReverse then + Quicksort.QuickSortEventReverse(0,R) else + Quicksort.QuickSortEvent(0,R); end; procedure TDynArray.CreateOrderedIndex(var aIndex: TIntegerDynArray; @@ -48732,7 +48718,7 @@ procedure TDynArray.CreateOrderedIndexAfterAdd(var aIndex: TIntegerDynArray; exit; if aIndex<>nil then begin // whole FillIncreasing(aIndex[]) for first time if ndx>=length(aIndex) then - SetLength(aIndex,ndx+ndx shr 3+64); // grow aIndex[] if needed + SetLength(aIndex,NextGrow(ndx)); // grow aIndex[] if needed aIndex[ndx] := ndx; end; CreateOrderedIndex(aIndex,aCompare); @@ -48857,7 +48843,7 @@ procedure TDynArray.Copy(const Source: TDynArray; ObjArrayByRef: boolean); if ElemType=nil then if not ObjArrayByRef and GetIsObjArray then LoadFromJSON(pointer(Source.SaveToJSON)) else - MoveFast(Source.fValue^^,fValue^^,n*ElemSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source.fValue^^,fValue^^,n*ElemSize) else CopyArray(fValue^,Source.fValue^,ElemType,n); end; @@ -48890,22 +48876,22 @@ function TDynArray.IndexOf(const Elem): PtrInt; P := fValue^; if @Elem<>nil then if ElemType=nil then - case ElemSize of - // optimized versions for arrays of byte,word,integer,Int64,Currency,Double - 1: for result := 0 to max do - if PByteArray(P)^[result]=byte(Elem) then exit; - 2: for result := 0 to max do - if PWordArray(P)^[result]=word(Elem) then exit; - 4: for result := 0 to max do // integer,single,32bitPointer - if PIntegerArray(P)^[result]=integer(Elem) then exit; - 8: for result := 0 to max do // Int64,Currency,Double,64bitPointer - if PInt64Array(P)^[result]=Int64(Elem) then exit; - else // generic binary comparison (fast with our overloaded CompareMemFixed) - for result := 0 to max do - if CompareMemFixed(P,@Elem,ElemSize) then - exit else - inc(PByte(P),ElemSize); - end else + case ElemSize of + // optimized versions for arrays of byte,word,integer,Int64,Currency,Double + 1: for result := 0 to max do + if PByteArray(P)^[result]=byte(Elem) then exit; + 2: for result := 0 to max do + if PWordArray(P)^[result]=word(Elem) then exit; + 4: for result := 0 to max do // integer,single,32bitPointer + if PIntegerArray(P)^[result]=integer(Elem) then exit; + 8: for result := 0 to max do // Int64,Currency,Double,64bitPointer + if PInt64Array(P)^[result]=Int64(Elem) then exit; + else // generic binary comparison (fast with our overloaded CompareMemFixed) + for result := 0 to max do + if CompareMemFixed(P,@Elem,ElemSize) then + exit else + inc(PByte(P),ElemSize); + end else case PTypeKind(ElemType)^ of tkLString{$ifdef FPC},tkLStringOld{$endif}: for result := 0 to max do @@ -49034,7 +49020,6 @@ procedure TDynArray.SetIsObjArray(aValue: boolean); procedure TDynArray.InternalSetLength(NewLength: PtrUInt); var p: PDynArrayRec; - pa: PAnsiChar absolute p; OldLength, NeededSize, minLength: PtrUInt; pp: pointer; i: integer; @@ -49052,13 +49037,6 @@ procedure TDynArray.InternalSetLength(NewLength: PtrUInt); {$ifdef FPC}FPCDynArrayClear{$else}_DynArrayClear{$endif}(fValue^,ArrayType); exit; end; - // retrieve old length - p := fValue^; - if p<>nil then begin - dec(PtrUInt(p),SizeOf(TDynArrayRec)); // p^ = start of heap object - OldLength := p^.length; - end else - OldLength := 0; // calculate the needed size of the resulting memory structure on heap NeededSize := NewLength*ElemSize+SizeOf(TDynArrayRec); {$ifndef CPU64} @@ -49067,29 +49045,38 @@ procedure TDynArray.InternalSetLength(NewLength: PtrUInt); [ArrayTypeShort^,NewLength]); {$endif} // if not shared (refCnt=1), resize; if shared, create copy (not thread safe) - if (p=nil) or (p^.refCnt=1) then begin - if NewLengthnil then - {$ifdef FPC}FPCFinalizeArray{$else}_FinalizeArray{$endif}( - pa+NeededSize,ElemType,OldLength-NewLength) else - if GetIsObjArray then begin // FreeAndNil() of resized objects list - for i := NewLength to OldLength-1 do - PObjectArray(fValue^)^[i].Free; - FillCharFast(pa[NeededSize],(OldLength-NewLength) shl POINTERSHR,0); - end; - ReallocMem(p,neededSize); + p := fValue^; + if p=nil then begin + p := AllocMem(NeededSize); + OldLength := NewLength; // no FillcharFast() below end else begin - InterlockedDecrement(PInteger(@p^.refCnt)^); // FPC has refCnt: PtrInt - GetMem(p,neededSize); - minLength := oldLength; - if minLength>newLength then - minLength := newLength; - if ElemType<>nil then begin - pp := pa+SizeOf(TDynArrayRec); - FillcharFast(pp^,minLength*elemSize,0); - CopyArray(pp,fValue^,ElemType,minLength); - end else - MoveFast(fValue^,pa[SizeOf(TDynArrayRec)],minLength*elemSize); + dec(PtrUInt(p),SizeOf(TDynArrayRec)); // p^ = start of heap object + OldLength := p^.length; + if p^.refCnt=1 then begin + if NewLengthnil then // release managed types in trailing items + {$ifdef FPC}FPCFinalizeArray{$else}_FinalizeArray{$endif}( + PAnsiChar(p)+NeededSize,ElemType,OldLength-NewLength) else + if GetIsObjArray then begin // FreeAndNil() of resized objects list + for i := NewLength to OldLength-1 do + PObjectArray(fValue^)^[i].Free; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}( + PAnsiChar(p)[NeededSize],(OldLength-NewLength) shl POINTERSHR,0); + end; + ReallocMem(p,NeededSize); + end else begin // make copy + InterlockedDecrement(PInteger(@p^.refCnt)^); // FPC has refCnt: PtrInt + GetMem(p,NeededSize); + minLength := oldLength; + if minLength>newLength then + minLength := newLength; + pp := PAnsiChar(p)+SizeOf(TDynArrayRec); + if ElemType<>nil then begin + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(pp^,minLength*elemSize,0); + CopyArray(pp,fValue^,ElemType,minLength); + end else + {$ifdef FPC}Move{$else}MoveFast{$endif}(fValue^^,pp^,minLength*elemSize); + end; end; // set refCnt=1 and new length to the heap memory structure with p^ do begin @@ -49100,11 +49087,12 @@ procedure TDynArray.InternalSetLength(NewLength: PtrUInt); length := newLength; {$endif} end; - inc(PByte(p),SizeOf(p^)); + inc(PByte(p),SizeOf(p^)); // p^ = start of dynamic aray items // reset new allocated elements content to zero if NewLength>OldLength then begin OldLength := OldLength*elemSize; - FillcharFast(pa[OldLength],neededSize-OldLength-SizeOf(TDynArrayRec),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}( + PAnsiChar(p)[OldLength],NewLength*ElemSize-OldLength,0); end; fValue^ := p; end; @@ -49139,7 +49127,7 @@ procedure TDynArray.SetCount(aCount: integer); c := PInteger(c)^; if capa>=c then exit; // no need to grow - inc(capa,capa shr 2); // growth factor = 1.5 + capa := NextGrow(capa); if capa0 then begin P := PAnsiChar(fValue^)+aFirstIndex*ElemSize; if ElemType=nil then - MoveFast(P^,D^^,aCount*ElemSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,D^^,aCount*ElemSize) else CopyArray(D^,P,ElemType,aCount); end; end; @@ -49222,7 +49210,7 @@ function TDynArray.AddArray(const DynArrayVar; aStartIndex, aCount: integer): in PS := pointer(PtrUInt(DynArrayVar)+cardinal(aStartIndex)*ElemSize); PD := pointer(PtrUInt(fValue^)+cardinal(n)*ElemSize); if ElemType=nil then - MoveFast(PS^,PD^,cardinal(aCount)*ElemSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(PS^,PD^,cardinal(aCount)*ElemSize) else CopyArray(PD,PS,ElemType,aCount); end; @@ -49234,7 +49222,7 @@ procedure TDynArray.ElemClear(var Elem); {$ifdef FPC}FPCFinalize{$else}_Finalize{$endif}(@Elem,ElemType) else if (fIsObjArray=oaTrue) or ((fIsObjArray=oaUnknown) and ComputeIsObjArray) then TObject(Elem).Free; - FillcharFast(Elem,ElemSize,0); // always fill with zero binary content + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Elem,ElemSize,0); // always end; function TDynArray.ElemLoad(Source: PAnsiChar): RawByteString; @@ -49242,7 +49230,7 @@ function TDynArray.ElemLoad(Source: PAnsiChar): RawByteString; if (Source<>nil) and (ElemType=nil) then SetString(result,Source,ElemSize) else begin SetString(result,nil,ElemSize); - FillcharFast(pointer(result)^,ElemSize,0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(pointer(result)^,ElemSize,0); ElemLoad(Source,pointer(result)^); end; end; @@ -49257,7 +49245,7 @@ procedure TDynArray.ElemLoad(Source: PAnsiChar; var Elem); begin if Source<>nil then // avoid GPF if ElemType=nil then - MoveFast(Source^,Elem,ElemSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(Source^,Elem,ElemSize) else ManagedTypeLoad(@Elem,Source,ElemType); end; @@ -49281,7 +49269,7 @@ function TDynArray.ElemLoadFind(Source: PAnsiChar): integer; exit; if ElemType=nil then data := Source else begin - FillCharFast(tmp,ElemSize,0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(tmp,ElemSize,0); ManagedTypeLoad(@tmp,Source,ElemType); if Source=nil then exit; @@ -49350,6 +49338,14 @@ procedure TDynArrayHashed.ElemCopy(const A; var B); begin InternalDynArray.ElemCopy(A,B); end; +function TDynArrayHashed.ElemPtr(index: PtrInt): pointer; +begin + result := InternalDynArray.ElemPtr(index); +end; +procedure TDynArrayHashed.ElemCopyAt(index: PtrInt; var Dest); +begin + InternalDynArray.ElemCopyAt(index,Dest); +end; function TDynArrayHashed.KnownType: TDynArrayKind; begin result := InternalDynArray.KnownType; @@ -49696,11 +49692,6 @@ function HashInteger(const Elem; Hasher: THasher): cardinal; result := Hasher(0,@Elem,SizeOf(integer)); end; -function HashCardinal(const Elem; Hasher: THasher): cardinal; -begin - result := Hasher(0,@Elem,SizeOf(cardinal)); -end; - function HashInt64(const Elem; Hasher: THasher): cardinal; begin result := Hasher(0,@Elem,SizeOf(Int64)); @@ -49747,7 +49738,7 @@ function VariantHash(const value: variant; CaseInsensitive: boolean; result := Hasher(VType,@VWord,2); varLongWord, varInteger, varSingle: result := Hasher(VType,@VLongWord,4); - varInt64, varDouble, varDate, varCurrency: + varInt64, varDouble, varDate, varCurrency, varWord64: result := Hasher(VType,@VInt64,SizeOf(Int64)); varString: if CaseInsensitive then @@ -50046,12 +50037,8 @@ function TObjectDynArrayWrapper.Add(Instance: TObject): integer; var cap: integer; begin cap := length(TObjectDynArray(fValue^)); - if cap<=fCount then begin - if cap<256 then - inc(cap,64) else - inc(cap,256+cap shr 3); - SetLength(TObjectDynArray(fValue^),cap); - end; + if cap<=fCount then + SetLength(TObjectDynArray(fValue^),NextGrow(cap)); result := fCount; TObjectDynArray(fValue^)[result] := Instance; inc(fCount); @@ -50065,7 +50052,8 @@ procedure TObjectDynArrayWrapper.Delete(Index: integer); TObjectDynArray(fValue^)[Index].Free; dec(fCount); if fCount>Index then - MoveFast(TObjectDynArray(fValue^)[Index+1],TObjectDynArray(fValue^)[Index], + {$ifdef FPC}Move{$else}MoveFast{$endif}( + TObjectDynArray(fValue^)[Index+1],TObjectDynArray(fValue^)[Index], (fCount-Index)*SizeOf(pointer)); end; @@ -50104,14 +50092,15 @@ function TObjectDynArrayWrapper.Capacity: integer; end; procedure TObjectDynArrayWrapper.Sort(Compare: TDynArraySortCompare); -var QuickSort: TDynArrayQuickSort; begin - if (@Compare<>nil) and (fCount>0) then begin - Quicksort.Compare := @Compare; - Quicksort.Value := fValue^; - Quicksort.ElemSize := SizeOf(pointer); - Quicksort.QuickSort(0,fCount-1); - end; + if (@Compare<>nil) and (fCount>0) then + QuickSortPtr(0,fCount-1,Compare,fValue^); +end; + +function NewSynLocker: PSynLocker; +begin + result := AllocMem(SizeOf(result^)); + result^.Init; end; function PtrArrayAdd(var aPtrArray; aItem: pointer): integer; @@ -50145,7 +50134,8 @@ function PtrArrayDelete(var aPtrArray; aItem: pointer): integer; exit; dec(n); if n>result then - MoveFast(a[result+1],a[result],(n-result)*SizeOf(pointer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + a[result+1],a[result],(n-result)*SizeOf(pointer)); SetLength(a,n); end; @@ -50157,7 +50147,7 @@ function PtrArrayFind(var aPtrArray; aItem: pointer): integer; { wrapper functions to T*ObjArr types } -function ObjArrayAdd(var aObjArray; aItem: TObject): integer; +function ObjArrayAdd(var aObjArray; aItem: TObject): PtrInt; var a: TObjectDynArray absolute aObjArray; begin result := length(a); @@ -50165,32 +50155,32 @@ function ObjArrayAdd(var aObjArray; aItem: TObject): integer; a[result] := aItem; end; -function ObjArrayAppend(var aDestObjArray, aSourceObjArray): integer; -var n: integer; +function ObjArrayAppend(var aDestObjArray, aSourceObjArray): PtrInt; +var n: PtrInt; s: TObjectDynArray absolute aSourceObjArray; d: TObjectDynArray absolute aDestObjArray; begin result := length(d); n := length(s); SetLength(d,result+n); - MoveFast(s[0],d[result],n*SizeOf(pointer)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(s[0],d[result],n*SizeOf(pointer)); s := nil; // s[] will be owned by d[] inc(result,n); end; -function ObjArrayAddCount(var aObjArray; aItem: TObject; var aObjArrayCount: integer): integer; +function ObjArrayAddCount(var aObjArray; aItem: TObject; var aObjArrayCount: integer): PtrInt; var a: TObjectDynArray absolute aObjArray; begin result := aObjArrayCount; if result=length(a) then - SetLength(a,result+result shr 3+16); + SetLength(a,NextGrow(result)); a[result] := aItem; inc(aObjArrayCount); end; procedure ObjArrayAddOnce(var aObjArray; aItem: TObject); var a: TObjectDynArray absolute aObjArray; - n: integer; + n: PtrInt; begin n := length(a); if not PtrUIntScanExists(pointer(a),n,PtrUInt(aItem)) then begin @@ -50204,14 +50194,14 @@ procedure ObjArraySetLength(var aObjArray; aLength: integer); SetLength(TObjectDynArray(aObjArray),aLength); end; -function ObjArrayFind(const aObjArray; aItem: TObject): integer; +function ObjArrayFind(const aObjArray; aItem: TObject): PtrInt; begin result := PtrUIntScanIndex(pointer(aObjArray), length(TObjectDynArray(aObjArray)),PtrUInt(aItem)); end; function ObjArrayCount(const aObjArray): integer; -var i: integer; +var i: PtrInt; a: TObjectDynArray absolute aObjArray; begin result := 0; @@ -50220,9 +50210,9 @@ function ObjArrayCount(const aObjArray): integer; inc(result); end; -procedure ObjArrayDelete(var aObjArray; aItemIndex: integer; +procedure ObjArrayDelete(var aObjArray; aItemIndex: PtrInt; aContinueOnException: boolean); -var n: integer; +var n: PtrInt; a: TObjectDynArray absolute aObjArray; begin n := length(a); @@ -50236,11 +50226,12 @@ procedure ObjArrayDelete(var aObjArray; aItemIndex: integer; a[aItemIndex].Free; dec(n); if n>aItemIndex then - MoveFast(a[aItemIndex+1],a[aItemIndex],(n-aItemIndex)*SizeOf(TObject)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + a[aItemIndex+1],a[aItemIndex],(n-aItemIndex)*SizeOf(TObject)); SetLength(a,n); end; -function ObjArrayDelete(var aObjArray; aItem: TObject): integer; +function ObjArrayDelete(var aObjArray; aItem: TObject): PtrInt; begin result := ObjArrayFind(aObjArray,aItem); if result>=0 then @@ -50248,16 +50239,9 @@ function ObjArrayDelete(var aObjArray; aItem: TObject): integer; end; procedure ObjArraySort(var aObjArray; Compare: TDynArraySortCompare); -var QuickSort: TDynArrayQuickSort; - n: integer; begin - n := length(TObjectDynArray(aObjArray)); - if (@Compare<>nil) and (n>0) then begin - Quicksort.Compare := @Compare; - Quicksort.Value := pointer(aObjArray); - Quicksort.ElemSize := SizeOf(pointer); - Quicksort.QuickSort(0,n-1); - end; + if @Compare<>nil then + QuickSortPtr(0,length(TObjectDynArray(aObjArray))-1,Compare,pointer(aObjArray)); end; procedure RawObjectsClear(o: PObject; n: integer); @@ -50293,7 +50277,7 @@ procedure ObjArrayClear(var aObjArray; aCount: integer); end; procedure ObjArrayClear(var aObjArray; aContinueOnException: boolean); -var n,i: integer; +var n,i: PtrInt; a: TObjectDynArray absolute aObjArray; begin n := length(a); @@ -50326,7 +50310,7 @@ function ObjArrayToJSON(const aObjArray; aOptions: TTextWriterWriteObjectOptions procedure ObjArrayObjArrayClear(var aObjArray); -var i: integer; +var i: PtrInt; a: TPointerDynArray absolute aObjArray; begin if a<>nil then begin @@ -50337,7 +50321,7 @@ procedure ObjArrayObjArrayClear(var aObjArray); end; procedure ObjArraysClear(const aObjArray: array of pointer); -var i: integer; +var i: PtrInt; begin for i := 0 to high(aObjArray) do if aObjArray[i]<>nil then @@ -50346,7 +50330,7 @@ procedure ObjArraysClear(const aObjArray: array of pointer); {$ifndef DELPHI5OROLDER} -function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): integer; +function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): PtrInt; var a: TInterfaceDynArray absolute aInterfaceArray; begin result := length(a); @@ -50356,7 +50340,7 @@ function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): integer; procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown); var a: TInterfaceDynArray absolute aInterfaceArray; - n: integer; + n: PtrInt; begin if PtrUIntScanExists(pointer(aInterfaceArray), length(TInterfaceDynArray(aInterfaceArray)),PtrUInt(aItem)) then @@ -50366,28 +50350,29 @@ procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown); a[n] := aItem; end; -function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): integer; +function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): PtrInt; begin result := PtrUIntScanIndex(pointer(aInterfaceArray), length(TInterfaceDynArray(aInterfaceArray)),PtrUInt(aItem)); end; -procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: integer); -var n: integer; +procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: PtrInt); +var n: PtrInt; a: TInterfaceDynArray absolute aInterfaceArray; begin n := length(a); - if cardinal(aItemIndex)>=cardinal(n) then + if PtrUInt(aItemIndex)>=PtrUInt(n) then exit; // out of range a[aItemIndex] := nil; dec(n); if n>aItemIndex then - MoveFast(a[aItemIndex+1],a[aItemIndex],(n-aItemIndex)*SizeOf(IInterface)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + a[aItemIndex+1],a[aItemIndex],(n-aItemIndex)*SizeOf(IInterface)); TPointerDynArray(aInterfaceArray)[n] := nil; // avoid GPF in SetLength() SetLength(a,n); end; -function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): integer; +function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): PtrInt; begin result := InterfaceArrayFind(aInterfaceArray,aItem); if result>=0 then @@ -50558,6 +50543,7 @@ destructor TAutoLock.Destroy; procedure TSynLocker.Init; begin + fSectionPadding := 0; InitializeCriticalSection(fSection); PaddingMaxUsedIndex := -1; fLocked := false; @@ -50574,6 +50560,12 @@ procedure TSynLocker.Done; fInitialized := false; end; +procedure TSynLocker.DoneAndFreeMem; +begin + Done; + FreeMem(@self); +end; + procedure TSynLocker.Lock; begin EnterCriticalSection(fSection); @@ -50838,13 +50830,13 @@ function TSynLocker.LockedPointerExchange(Index: integer; Value: pointer): point constructor TInterfacedObjectLocked.Create; begin inherited Create; - fSafe.Init; + fSafe := NewSynLocker; end; destructor TInterfacedObjectLocked.Destroy; begin inherited Destroy; - fSafe.Done; + fSafe^.DoneAndFreeMem; end; @@ -50884,12 +50876,9 @@ procedure TSynPersistent.Assign(Source: TSynPersistent); {$ifdef FPC_OR_PUREPASCAL} class function TSynPersistent.NewInstance: TObject; -var p: pointer; begin // bypass vmtIntfTable and vmt^.vInitTable (management operators) - GetMem(p, InstanceSize); - FillCharFast(p^, InstanceSize, 0); - PPointer(p)^ := pointer(self); // store VMT - result := p; + result := AllocMem(InstanceSize); // will zero memory + PPointer(result)^ := pointer(self); // store VMT end; {$else} class function TSynPersistent.NewInstance: TObject; @@ -50942,398 +50931,21 @@ procedure TSynPersistent.FreeInstance; {$endif FPC_OR_PUREPASCAL} -{ TSynPersistentLocked } - -constructor TSynPersistentLocked.Create; -begin - inherited Create; - fSafe.Init; -end; - -destructor TSynPersistentLocked.Destroy; -begin - inherited Destroy; - fSafe.Done; -end; - - { TSynPersistentLock } constructor TSynPersistentLock.Create; begin inherited Create; - GetMem(fSafe,SizeOf(TSynLocker)); - FillCharFast(fSafe^,SizeOf(TSynLocker),0); - fSafe^.Init; + fSafe := NewSynLocker; end; destructor TSynPersistentLock.Destroy; begin - fSafe^.Done; - FreeMem(fSafe); + fSafe^.DoneAndFreeMem; inherited; end; -{ TSynPersistentStore } - -constructor TSynPersistentStore.Create(const aName: RawUTF8); -begin - Create; - fName := aName; -end; - -constructor TSynPersistentStore.CreateFrom(const aBuffer: RawByteString; - aLoad: TAlgoCompressLoad); -begin - CreateFromBuffer(pointer(aBuffer),length(aBuffer),aLoad); -end; - -constructor TSynPersistentStore.CreateFromBuffer( - aBuffer: pointer; aBufferLen: integer; aLoad: TAlgoCompressLoad); -begin - Create(''); - LoadFrom(aBuffer,aBufferLen,aLoad); -end; - -constructor TSynPersistentStore.CreateFromFile(const aFileName: TFileName; - aLoad: TAlgoCompressLoad); -begin - Create(''); - LoadFromFile(aFileName,aLoad); -end; - -procedure TSynPersistentStore.LoadFromReader; -begin - fReader.VarUTF8(fName); -end; - -procedure TSynPersistentStore.SaveToWriter(aWriter: TFileBufferWriter); -begin - aWriter.Write(fName); -end; - -procedure TSynPersistentStore.LoadFrom(const aBuffer: RawByteString; - aLoad: TAlgoCompressLoad); -begin - if aBuffer <> '' then - LoadFrom(pointer(aBuffer),length(aBuffer),aLoad); -end; - -procedure TSynPersistentStore.LoadFrom(aBuffer: pointer; aBufferLen: integer; - aLoad: TAlgoCompressLoad); -var localtemp: RawByteString; - p: pointer; - temp: PRawByteString; -begin - if (aBuffer=nil) or (aBufferLen<=0) then - exit; // nothing to load - fLoadFromLastAlgo := TAlgoCompress.Algo(aBuffer,aBufferLen); - if fLoadFromLastAlgo = nil then - fReader.ErrorData('%.LoadFrom unknown TAlgoCompress AlgoID=%', - [self,PByteArray(aBuffer)[4]]); - temp := fReaderTemp; - if temp=nil then - temp := @localtemp; - p := fLoadFromLastAlgo.Decompress(aBuffer,aBufferLen,fLoadFromLastUncompressed,temp^,aLoad); - if p=nil then - fReader.ErrorData('%.LoadFrom %.Decompress failed',[self,fLoadFromLastAlgo]); - fReader.Init(p,fLoadFromLastUncompressed); - LoadFromReader; -end; - -function TSynPersistentStore.LoadFromFile(const aFileName: TFileName; - aLoad: TAlgoCompressLoad): boolean; -var temp: RawByteString; -begin - temp := StringFromFile(aFileName); - result := temp<>''; - if result then - LoadFrom(temp,aLoad); -end; - -procedure TSynPersistentStore.SaveTo(out aBuffer: RawByteString; nocompression: boolean; - BufLen: integer; ForcedAlgo: TAlgoCompress); -var writer: TFileBufferWriter; - temp: array[word] of byte; -begin - if BufLen<=SizeOf(temp) then - writer := TFileBufferWriter.Create(TRawByteStringStream,@temp,SizeOf(temp)) else - writer := TFileBufferWriter.Create(TRawByteStringStream,BufLen); - try - SaveToWriter(writer); - fSaveToLastUncompressed := writer.TotalWritten; - aBuffer := writer.FlushAndCompress(nocompression,ForcedAlgo); - finally - writer.Free; - end; -end; - -function TSynPersistentStore.SaveTo(nocompression: boolean; BufLen: integer; - ForcedAlgo: TAlgoCompress): RawByteString; -begin - SaveTo(result,nocompression,BufLen,ForcedAlgo); -end; - -function TSynPersistentStore.SaveToFile(const aFileName: TFileName; - nocompression: boolean; BufLen: integer; ForcedAlgo: TAlgoCompress): PtrUInt; -var temp: RawByteString; -begin - SaveTo(temp,nocompression,BufLen,ForcedAlgo); - if FileFromString(temp,aFileName) then - result := length(temp) else - result := 0; -end; - - -{ TSynPersistentStoreJson } - -procedure TSynPersistentStoreJson.AddJSON(W: TTextWriter); -begin - W.AddPropJSONString('name', fName); -end; - -function TSynPersistentStoreJson.SaveToJSON(reformat: TTextWriterJSONFormat): RawUTF8; -var - W: TTextWriter; -begin - W := TTextWriter.CreateOwnedStream(65536); - try - W.Add('{'); - AddJSON(W); - W.CancelLastComma; - W.Add('}'); - W.SetText(result, reformat); - finally - W.Free; - end; -end; - - -{ TSynUniqueIdentifierBits } - -function TSynUniqueIdentifierBits.Counter: word; -begin - result := PWord(@Value)^ and $7fff; -end; - -function TSynUniqueIdentifierBits.ProcessID: TSynUniqueIdentifierProcess; -begin - result := (PCardinal(@Value)^ shr 15) and $ffff; -end; - -function TSynUniqueIdentifierBits.CreateTimeUnix: TUnixTime; -begin - result := Value shr 31; -end; - -{$ifndef NOVARIANTS} -function TSynUniqueIdentifierBits.AsVariant: variant; -begin - ToVariant(result); -end; - -procedure TSynUniqueIdentifierBits.ToVariant(out result: variant); -begin - TDocVariantData(result).InitObject(['Created',DateTimeToIso8601Text(CreateDateTime), - 'Identifier',ProcessID,'Counter',Counter,'Value',Value, - 'Hex',Int64ToHex(Value)],JSON_OPTIONS_FAST); -end; -{$endif NOVARIANTS} - -{$ifndef DELPHI5OROLDER} -function TSynUniqueIdentifierBits.Equal(const Another: TSynUniqueIdentifierBits): boolean; -begin - result := Value=Another.Value; -end; -{$endif} - -procedure TSynUniqueIdentifierBits.From(const AID: TSynUniqueIdentifier); -begin - Value := AID; -end; - -function TSynUniqueIdentifierBits.CreateTimeLog: TTimeLog; -begin - PTimeLogBits(@result)^.From(UnixTimeToDateTime(Value shr 31)); -end; - -function TSynUniqueIdentifierBits.CreateDateTime: TDateTime; -begin - result := UnixTimeToDateTime(Value shr 31); -end; - -function TSynUniqueIdentifierBits.ToHexa: RawUTF8; -begin - Int64ToHex(Value,result); -end; - -function TSynUniqueIdentifierBits.FromHexa(const hexa: RawUTF8): boolean; -begin - result := (Length(hexa)=16) and HexDisplayToBin(pointer(hexa),@Value,SizeOf(Value)); -end; - -procedure TSynUniqueIdentifierBits.FromDateTime(const aDateTime: TDateTime); -begin - Value := DateTimeToUnixTime(aDateTime) shl 31; -end; - -procedure TSynUniqueIdentifierBits.FromUnixTime(const aUnixTime: TUnixTime); -begin - Value := aUnixTime shl 31; -end; - - -{ TSynUniqueIdentifierGenerator } - -const // fSafe.Padding[] slots - SYNUNIQUEGEN_COMPUTECOUNT = 0; - -procedure TSynUniqueIdentifierGenerator.ComputeNew( - out result: TSynUniqueIdentifierBits); -var currentTime: cardinal; -begin - currentTime := UnixTimeUTC; // fast API (under Windows, faster than GetTickCount64) - fSafe.Lock; - try - if currentTime>fUnixCreateTime then begin - fUnixCreateTime := currentTime; - fLastCounter := 0; // reset - end; - if fLastCounter=$7fff then begin // collision (unlikely) -> cheat on timestamp - inc(fUnixCreateTime); - fLastCounter := 0; - end else - inc(fLastCounter); - result.Value := Int64(fLastCounter or fIdentifierShifted) or - (Int64(fUnixCreateTime) shl 31); - inc(fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT].VInt64); - finally - fSafe.UnLock; - end; -end; - -function TSynUniqueIdentifierGenerator.ComputeNew: Int64; -begin - ComputeNew(PSynUniqueIdentifierBits(@result)^); -end; - -function TSynUniqueIdentifierGenerator.GetComputedCount: Int64; -begin - {$ifdef NOVARIANTS} - fSafe.Lock; - result := fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT].VInt64; - fSafe.Unlock; - {$else} - result := fSafe.LockedInt64[SYNUNIQUEGEN_COMPUTECOUNT]; - {$endif} -end; - -procedure TSynUniqueIdentifierGenerator.ComputeFromDateTime(const aDateTime: TDateTime; - out result: TSynUniqueIdentifierBits); -begin // assume fLastCounter=0 - ComputeFromUnixTime(DateTimeToUnixTime(aDateTime),result); -end; - -procedure TSynUniqueIdentifierGenerator.ComputeFromUnixTime(const aUnixTime: TUnixTime; - out result: TSynUniqueIdentifierBits); -begin // assume fLastCounter=0 - result.Value := aUnixTime shl 31; - if self<>nil then - result.Value := result.Value or fIdentifierShifted; -end; - -constructor TSynUniqueIdentifierGenerator.Create(aIdentifier: TSynUniqueIdentifierProcess; - const aSharedObfuscationKey: RawUTF8); -var i, len: integer; - crc: cardinal; -begin - fIdentifier := aIdentifier; - fIdentifierShifted := aIdentifier shl 15; - fSafe.Init; - {$ifdef NOVARIANTS} - variant(fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT]) := 0; - {$else} - fSafe.LockedInt64[SYNUNIQUEGEN_COMPUTECOUNT] := 0; - {$endif} - // compute obfuscation key using hash diffusion of the supplied text - len := length(aSharedObfuscationKey); - crc := crc32ctab[0,len and 1023]; - for i := 0 to high(fCrypto)+1 do begin - crc := crc32ctab[0,crc and 1023] xor crc32ctab[3,i] xor - kr32(crc,pointer(aSharedObfuscationKey),len) xor - crc32c(crc,pointer(aSharedObfuscationKey),len) xor - fnv32(crc,pointer(aSharedObfuscationKey),len); - // do not modify those hashes above or you will break obfuscation pattern! - if i<=high(fCrypto) then - fCrypto[i] := crc else - fCryptoCRC := crc; - end; - // due to the weakness of the hash algorithms used, this approach is a bit - // naive and would be broken easily with brute force - but point here is to - // hide/obfuscate public values at end-user level (e.g. when publishing URIs), - // not implement strong security, so it sounds good enough for our purpose -end; - -destructor TSynUniqueIdentifierGenerator.Destroy; -begin - fSafe.Done; - FillcharFast(fCrypto,SizeOf(fCrypto),0); - fCryptoCRC := 0; - inherited Destroy; -end; - -type // compute a 24 hexadecimal chars (96 bits) obfuscated pseudo file name - TSynUniqueIdentifierObfuscatedBits = packed record - crc: cardinal; - id: TSynUniqueIdentifierBits; - end; - -function TSynUniqueIdentifierGenerator.ToObfuscated( - const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated; -var bits: TSynUniqueIdentifierObfuscatedBits; - key: cardinal; -begin - result := ''; - if aIdentifier=0 then - exit; - bits.id.Value := aIdentifier; - if self=nil then - key := 0 else - key := crc32ctab[0,bits.id.ProcessID and 1023] xor fCryptoCRC; - bits.crc := crc32c(bits.id.ProcessID,@bits.id,SizeOf(bits.id)) xor key; - if self<>nil then - bits.id.Value := bits.id.Value xor PInt64(@fCrypto[high(fCrypto)-1])^; - result := BinToHex(@bits,SizeOf(bits)); -end; - -function TSynUniqueIdentifierGenerator.FromObfuscated( - const aObfuscated: TSynUniqueIdentifierObfuscated; - out aIdentifier: TSynUniqueIdentifier): boolean; -var bits: TSynUniqueIdentifierObfuscatedBits; - len: integer; - key: cardinal; -begin - result := false; - len := PosEx('.',aObfuscated); - if len=0 then - len := Length(aObfuscated) else - dec(len); // trim right '.jpg' - if (len<>SizeOf(bits)*2) or - not SynCommons.HexToBin(pointer(aObfuscated),@bits,SizeOf(bits)) then - exit; - if self=nil then - key := 0 else begin - bits.id.Value := bits.id.Value xor PInt64(@fCrypto[high(fCrypto)-1])^; - key := crc32ctab[0,bits.id.ProcessID and 1023] xor fCryptoCRC; - end; - if crc32c(bits.id.ProcessID,@bits.id,SizeOf(bits.id)) xor key=bits.crc then begin - aIdentifier := bits.id.Value; - result := true; - end; -end; - - { TObjectListSorted } destructor TObjectListSorted.Destroy; @@ -51377,9 +50989,10 @@ procedure TObjectListSorted.InsertNew(Item: TSynPersistentLock; Index: integer); begin if fCount=length(fObjArray) then - SetLength(fObjArray,fCount+256+fCount shr 3); + SetLength(fObjArray,NextGrow(fCount)); if cardinal(Index)i then - MoveFast(fObjArray[i+1],fObjArray[i],(fCount-i)*SizeOf(TObject)); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + fObjArray[i+1],fObjArray[i],(fCount-i)*SizeOf(TObject)); result := true; end; finally @@ -51440,6 +51054,36 @@ function TObjectListSorted.FindOrAddLocked(const Value; out added: boolean): poi { TTextWriter } +procedure TTextWriter.CancelLastChar; +begin + if B>=fTempBuf then // Add() methods append at B+1 + dec(B); +end; + +function TTextWriter.LastChar: AnsiChar; +begin + if B>=fTempBuf then + result := B^ else + result := #0; // returns #0 if no char has been written yet +end; + +procedure TTextWriter.CancelLastChar(aCharToCancel: AnsiChar); +begin + if (B>=fTempBuf) and (B^=aCharToCancel) then + dec(B); +end; + +function TTextWriter.PendingBytes: PtrUInt; +begin + result := B-fTempBuf+1; +end; + +procedure TTextWriter.CancelLastComma; +begin + if (B>=fTempBuf) and (B^=',') then + dec(B); +end; + procedure TTextWriter.Add(Value: PtrInt); var tmp: array[0..23] of AnsiChar; P: PAnsiChar; @@ -51454,7 +51098,7 @@ procedure TTextWriter.Add(Value: PtrInt); P := StrInt32(@tmp[23],value); Len := @tmp[23]-P; end; - MoveFast(P[0],B[1],Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[0],B[1],Len); inc(B,Len); end; @@ -51476,7 +51120,7 @@ procedure TTextWriter.AddCurr64(const Value: Int64); dec(Len,3) else dec(Len,2) else dec(Len); - MoveFast(P[0],B[1],Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[0],B[1],Len); inc(B,Len); end; @@ -51533,7 +51177,7 @@ procedure TTextWriter.AddDateTime(Value: PDateTime; FirstChar: AnsiChar; end; end; -procedure TTextWriter.AddDateTime(const Value: TDateTime; WithMS: boolean=false); +procedure TTextWriter.AddDateTime(const Value: TDateTime; WithMS: boolean); begin if Value=0 then exit; @@ -51553,34 +51197,34 @@ procedure TTextWriter.AddDateTime(const Value: TDateTime; WithMS: boolean=false) dec(B); end; -procedure TTextWriter.AddDateTimeMS(const Value: TDateTime; Expanded: boolean=true; - FirstTimeChar: AnsiChar='T'; const TZD: RawUTF8='Z'); -var HH,MM,SS,MS,Y,M,D: word; +procedure TTextWriter.AddDateTimeMS(const Value: TDateTime; Expanded: boolean; + FirstTimeChar: AnsiChar; const TZD: RawUTF8); +var T: TSynSystemTime; begin if Value=0 then exit; - DecodeDate(Value,Y,M,D); - DecodeTime(Value,HH,MM,SS,MS); - Add(DTMS_FMT[Expanded], [UInt4DigitsToShort(Y),UInt2DigitsToShortFast(M), - UInt2DigitsToShortFast(D),FirstTimeChar,UInt2DigitsToShortFast(HH), - UInt2DigitsToShortFast(MM),UInt2DigitsToShortFast(SS),UInt3DigitsToShort(MS),TZD]); + T.FromDateTime(Value); + Add(DTMS_FMT[Expanded], [UInt4DigitsToShort(T.Year), + UInt2DigitsToShortFast(T.Month),UInt2DigitsToShortFast(T.Day),FirstTimeChar, + UInt2DigitsToShortFast(T.Hour),UInt2DigitsToShortFast(T.Minute), + UInt2DigitsToShortFast(T.Second),UInt3DigitsToShort(T.MilliSecond),TZD]); end; procedure TTextWriter.AddU(Value: cardinal); -var tmp: array[0..15] of AnsiChar; +var tmp: array[0..23] of AnsiChar; P: PAnsiChar; Len: integer; begin - if BEnd-B<=16 then + if BEnd-B<=24 then FlushToStream; if Value<=high(SmallUInt32UTF8) then begin P := pointer(SmallUInt32UTF8[Value]); Len := {$ifdef FPC}_LStrLenP(P){$else}PInteger(P-4)^{$endif}; end else begin - P := StrUInt32(@tmp[15],Value); - Len := @tmp[15]-P; + P := StrUInt32(@tmp[23],Value); + Len := @tmp[23]-P; end; - MoveFast(P[0],B[1],Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[0],B[1],Len); inc(B,Len); end; @@ -51599,7 +51243,7 @@ procedure TTextWriter.AddQ(Value: QWord); P := StrUInt64(@tmp[23],Value); Len := @tmp[23]-P; end; - MoveFast(P[0],B[1],Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[0],B[1],Len); inc(B,Len); end; @@ -51664,10 +51308,10 @@ procedure TTextWriter.Add(Value: Int64); P := StrUInt64(@tmp[23],Value); Len := @tmp[23]-P; end; - MoveFast(P[0],B[1],Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P[0],B[1],Len); inc(B,Len); end; -{$endif} +{$endif CPU64} procedure TTextWriter.Add(Value: boolean); begin @@ -51695,7 +51339,7 @@ procedure TTextWriter.AddFloatStr(P: PUTF8Char); B^ := '0'; // '.5' -> '0.5' inc(B); end; - MoveFast(P^,B^,L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,B^,L); inc(B,L-1); end; end; @@ -51768,7 +51412,7 @@ procedure TTextWriter.AddCRAndIndent; if BEnd-B<=Integer(ntabs)+1 then FlushToStream; PWord(B+1)^ := 13+10 shl 8; // CR + LF - FillcharFast(B[3],ntabs,9); // indentation using tabs + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(B[3],ntabs,9); // #9=tab inc(B,ntabs+2); end; @@ -51780,7 +51424,7 @@ procedure TTextWriter.AddChars(aChar: AnsiChar; aCount: integer); if aCount99 then MS := 99; - PWord(B)^:= TwoDigitLookupW[MS]; + PWord(B)^:= W[MS]; inc(B,9); end; @@ -52151,7 +51801,7 @@ procedure TTextWriter.AddVoidRecordJSON(TypeInfo: pointer); var tmp: TBytes; info: PTypeInfo; begin - info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet); + info := GetTypeInfo(TypeInfo,tkRecordKinds); if (self=nil) or (info=nil) then raise ESynException.CreateUTF8('Invalid %.AddVoidRecordJSON(%)',[self,TypeInfo]); SetLength(tmp,info^.recSize {$ifdef FPC}and $7FFFFFFF{$endif}); @@ -52300,7 +51950,7 @@ procedure TTextWriter.AddTypedJSON(aTypeInfo: pointer; const aValue); if twoEnumSetsAsBooleanInRecord in fCustomOptions then begin Add('{'); for i := 0 to max do begin - AddPS(GetBit(aValue,i)); + AddPS(GetBitPtr(@aValue,i)); Add(','); inc(PByte(PS),ord(PS^[0])+1); // next short string end; @@ -52313,7 +51963,7 @@ procedure TTextWriter.AddTypedJSON(aTypeInfo: pointer; const aValue); GetAllBits(cardinal(aValue),max+1) then AddShort('"*"') else begin for i := 0 to max do begin - if GetBit(aValue,i) then begin + if GetBitPtr(@aValue,i) then begin AddPS; Add(','); end; @@ -52703,34 +52353,12 @@ procedure TTextWriter.AddLine(const Text: shortstring); if BEnd-B<=ord(Text[0])+2 then FlushToStream; inc(B); - MoveFast(Text[1],B[0],ord(Text[0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Text[1],B[0],ord(Text[0])); inc(B,ord(Text[0])); PWord(B)^ := 13+10 shl 8; // CR + LF inc(B); end; -procedure TTextWriter.AddPointer(P: PtrUInt); - procedure Pointer4ToHex(B: PWordArray; P: PtrUInt); - begin - B[3] := TwoDigitsHexWB[ToByte(P)]; P := P shr 8; - B[2] := TwoDigitsHexWB[ToByte(P)]; P := P shr 8; - B[1] := TwoDigitsHexWB[ToByte(P)]; P := P shr 8; - B[0] := TwoDigitsHexWB[P]; - end; -begin - if BEnd-B<=SizeOf(P)*2 then - FlushToStream; - {$ifdef CPU64} // truncate to for most heap-allocated 4 bytes pointers - if P and $ffffffff00000000<>0 then begin - BinToHexDisplay(@P,PAnsiChar(B+1),8); - inc(B,16); - exit; - end; - {$endif} - Pointer4ToHex(@B[1],P); - inc(B,8); -end; - procedure TTextWriter.AddBinToHexDisplay(Bin: pointer; BinBytes: integer); begin if cardinal(BinBytes*2-1)>=cardinal(fTempBufSize) then @@ -52764,8 +52392,27 @@ procedure TTextWriter.AddBinToHexDisplayQuoted(Bin: pointer; BinBytes: integer); inc(B,2); end; +procedure TTextWriter.AddBinToHexDisplayMinChars(Bin: pointer; BinBytes: PtrInt); +begin + if (BinBytes<=0) or (cardinal(BinBytes*2-1)>=cardinal(fTempBufSize)) then + exit; + repeat // append hexa chars up to the last non zero byte + dec(BinBytes); + until (BinBytes=0) or (PByteArray(Bin)[BinBytes]<>0); + inc(BinBytes); + if BEnd-B<=BinBytes*2 then + FlushToStream; + BinToHexDisplayLower(Bin,PAnsiChar(B+1),BinBytes); + inc(B,BinBytes*2); +end; + +procedure TTextWriter.AddPointer(P: PtrUInt); +begin + AddBinToHexDisplayMinChars(@P,SizeOf(P)); +end; + procedure TTextWriter.AddBinToHex(Bin: Pointer; BinBytes: integer); -var ChunkBytes: integer; +var ChunkBytes: PtrInt; begin if BinBytes<=0 then exit; @@ -53095,7 +52742,7 @@ procedure TTextWriter.AddNoJSONEscape(P: Pointer; Len: PtrInt); if Len2) and (PInteger(s)^ and $ffffff=JSON_BASE64_MAGIC) then begin AddNoJSONEscape(pointer(s),L); // identified as a BLOB content exit; end; @@ -53264,11 +52911,15 @@ procedure TTextWriter.AddAnyAnsiBuffer(P: PAnsiChar; Len: integer; JSON_ESCAPE_BYTE: TSynByteBoolean; function NeedsJsonEscape(const Text: RawUTF8): boolean; -var i: integer; +var tab: ^TSynByteBoolean; + P: PByteArray; + i: PtrInt; begin result := true; - for i := 1 to length(Text) do - if byte(Text[i]) in JSON_ESCAPE then + tab := @JSON_ESCAPE_BYTE; + P := pointer(Text); + for i := 0 to length(Text)-1 do + if tab[P^[i]] then exit; result := false; end; @@ -53379,7 +53030,7 @@ procedure TTextWriter.AddOnSameLineW(P: PWord; Len: PtrInt); procedure TTextWriter.AddJSONEscape(P: Pointer; Len: PtrInt); var i,c: PtrInt; - {$ifndef CPUX86}tab: ^TSynByteBoolean;{$endif} + {$ifndef CPUX86NOTPIC}tab: ^TSynByteBoolean;{$endif} label noesc; begin if P=nil then @@ -53387,8 +53038,8 @@ procedure TTextWriter.AddJSONEscape(P: Pointer; Len: PtrInt); if Len=0 then Len := MaxInt; i := 0; - {$ifdef CPUX86} - while i=Len) or (PByteArray(P)[i] in JSON_ESCAPE); {$else} tab := @JSON_ESCAPE_BYTE; - while i=Len) or tab^[PByteArray(P)[i]]; - {$endif} + {$endif CPUX86NOTPIC} inc(PByte(P),c); dec(i,c); dec(Len,c); if BEnd-B<=i then AddNoJSONEscape(P,i) else begin - MoveFast(P^,B[1],i); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,B[1],i); inc(B,i); end; if i>=Len then - break; + exit; end; repeat c := PByteArray(P)[i]; @@ -53439,7 +53090,7 @@ procedure TTextWriter.AddJSONEscape(P: Pointer; Len: PtrInt); if i>=Len then exit; until false; - end; + until i>=Len; end; procedure TTextWriter.AddJSONEscapeW(P: PWord; Len: PtrInt); @@ -53543,7 +53194,7 @@ procedure TTextWriter.Add(const V: TVarRec; Escape: TTextWriterKind; {$endif} vtString: if VString^[0]<>#0 then Add(@VString^[1],ord(VString^[0]),Escape); vtInterface, - vtPointer: AddPointer(PtrUInt(VPointer)); + vtPointer: AddBinToHexDisplayMinChars(@VPointer,SizeOf(VPointer)); vtPChar: Add(PUTF8Char(VPChar),Escape); vtObject: WriteObject(VObject,WriteObjectOptions); vtClass: AddClassName(VClass); @@ -53653,7 +53304,7 @@ procedure TTextWriter.AddNoJSONEscapeString(const s: string); begin if s<>'' then {$ifdef UNICODE} - AddNoJSONEscapeW(pointer(s),length(s)); + AddNoJSONEscapeW(pointer(s),0); {$else} AddAnsiString(s,twNone); {$endif} @@ -53681,12 +53332,12 @@ procedure TTextWriter.AddPropName(const PropName: ShortString); if BEnd-B<=ord(PropName[0])+3 then FlushToStream; if twoForceJSONExtended in CustomOptions then begin - MoveFast(PropName[1],B[1],ord(PropName[0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}(PropName[1],B[1],ord(PropName[0])); inc(B,ord(PropName[0])+1); B^ := ':'; end else begin B[1] := '"'; - MoveFast(PropName[1],B[2],ord(PropName[0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}(PropName[1],B[2],ord(PropName[0])); inc(B,ord(PropName[0])+2); PWord(B)^ := ord('"')+ord(':')shl 8; inc(B); @@ -53717,7 +53368,7 @@ procedure TTextWriter.AddFieldName(FieldName: PUTF8Char; FieldNameLen: integer); if BEnd-B<=FieldNameLen+3 then FlushToStream; B[1] := '"'; - MoveFast(FieldName^,B[2],FieldNameLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(FieldName^,B[2],FieldNameLen); inc(B,FieldNameLen+2); PWord(B)^ := ord('"')+ord(':')shl 8; inc(B); @@ -53736,19 +53387,21 @@ procedure TTextWriter.AddInstanceName(Instance: TObject; SepChar: AnsiChar); AddShort('void') else AddShort(PShortString(PPointer(PPtrInt(Instance)^+vmtClassName)^)^); Add('('); - AddPointer(PtrUInt(Instance)); + AddBinToHexDisplayMinChars(@Instance,SizeOf(Instance)); Add(')','"'); if SepChar<>#0 then Add(SepChar); end; procedure TTextWriter.AddInstancePointer(Instance: TObject; SepChar: AnsiChar; - IncludeUnitName: boolean); + IncludeUnitName, IncludePointer: boolean); begin AddShort(PShortString(PPointer(PPtrInt(Instance)^+vmtClassName)^)^); - Add('('); - AddPointer(PtrUInt(Instance)); - Add(')'); + if IncludePointer then begin + Add('('); + AddBinToHexDisplayMinChars(@Instance,SizeOf(Instance)); + Add(')'); + end; if SepChar<>#0 then Add(SepChar); end; @@ -53759,7 +53412,7 @@ procedure TTextWriter.AddShort(const Text: ShortString); exit; if BEnd-B<=ord(Text[0]) then FlushToStream; - MoveFast(Text[1],B[1],ord(Text[0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Text[1],B[1],ord(Text[0])); inc(B,ord(Text[0])); end; @@ -53816,7 +53469,7 @@ procedure TTextWriter.AddString(const Text: RawUTF8); if Lnil then begin EchoFlush; fEchoStart := 0; end; i := B-fTempBuf+1; + if i<=0 then + exit; fStream.WriteBuffer(fTempBuf^,i); inc(fTotalFileSize,i); if not (twoFlushToStreamNoAutoResize in fCustomOptions) and - not (twoBufferIsExternal in fCustomOptions) and - (fTempBufSize<49152) and - (fTotalFileSize-fInitialStreamPosition>1 shl 18) then begin - FreeMem(fTempBuf); // with big content (256KB) comes bigger buffer (64KB) - fTempBufSize := 65536; - GetMem(fTempBuf,65536); - BEnd := fTempBuf+(65536-2); + not (twoBufferIsExternal in fCustomOptions) then begin + written := fTotalFileSize-fInitialStreamPosition; + if (fTempBufSize<49152) and (written>1 shl 18) then // 256KB -> 64KB buffer + written := 65536 else + if (fTempBufSize<1 shl 20) and (written>40 shl 20) then // 40MB -> 1MB buffer + written := 1 shl 20 else + written := 0; + if written>0 then begin + fTempBufSize := written; + FreeMem(fTempBuf); // with big content comes bigger buffer + GetMem(fTempBuf,fTempBufSize); + BEnd := fTempBuf+(fTempBufSize-2); + end; end; B := fTempBuf-1; end; @@ -54028,11 +53665,11 @@ procedure TTextWriter.SetEndOfLineCRLF(aEndOfLineCRLF: boolean); exclude(fCustomOptions,twoEndOfLineCRLF); end; -function TTextWriter.GetLength: cardinal; +function TTextWriter.GetTextLength: PtrUInt; begin if self=nil then result := 0 else - result := cardinal(B-fTempBuf+1)+fTotalFileSize-fInitialStreamPosition; + result := PtrUInt(B-fTempBuf+1)+fTotalFileSize-fInitialStreamPosition; end; function TTextWriter.Text: RawUTF8; @@ -54157,8 +53794,8 @@ procedure TTextWriter.EchoRemove(const aEcho: TOnTextWriterEcho); MultiEventRemove(fEchos,TMethod(aEcho)); end; -function TTextWriter.EchoFlush: integer; -var L,LI: Integer; +function TTextWriter.EchoFlush: PtrInt; +var L,LI: PtrInt; P: PByteArray; begin result := B-fTempBuf+1; @@ -54168,7 +53805,7 @@ function TTextWriter.EchoFlush: integer; dec(L); LI := length(fEchoBuf); // fast append to fEchoBuf SetLength(fEchoBuf,LI+L); - MoveFast(P^,PByteArray(fEchoBuf)[LI],L); + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,PByteArray(fEchoBuf)[LI],L); end; procedure TTextWriter.EchoReset; @@ -54354,7 +53991,7 @@ function JSONDecode(P: PUTF8Char; const Names: array of RawUTF8; if Values=nil then exit; // avoid GPF n := length(Names); - FillcharFast(Values[0],n*SizeOf(Values[0]),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Values[0],n*SizeOf(Values[0]),0); dec(n); if P=nil then exit; @@ -54371,7 +54008,7 @@ function JSONDecode(P: PUTF8Char; const Names: array of RawUTF8; if not(EndOfObject in [',','}']) then exit; // invalid item separator for i := 0 to n do - if IdemPropNameU(Names[i],name,namelen) then begin + if (Values[i].Value=nil) and IdemPropNameU(Names[i],name,namelen) then begin Values[i].Value := value; Values[i].ValueLen := valuelen; break; @@ -54479,7 +54116,7 @@ function GetJSONField(P: PUTF8Char; out PDest: PUTF8Char; wasString: PBoolean; EndOfObject: PUTF8Char; Len: PInteger): PUTF8Char; var D: PUTF8Char; b,c4,surrogate,j: integer; - tab: {$ifdef CPUX86}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTableByte absolute ConvertHexToBin{$else}PNormTableByte{$endif}; label slash,num; begin if wasString<>nil then @@ -54562,7 +54199,7 @@ function GetJSONField(P: PUTF8Char; out PDest: PUTF8Char; 'f': D^ := #$0c; 'r': D^ := #$0d; 'u': begin // inlined decoding of '\u0123' UTF-16 codepoint into UTF-8 - {$ifndef CPUX86}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @ConvertHexToBin;{$endif} // faster on PIC and x86_64 c4 := tab[ord(P[1])]; if c4<=15 then begin b := tab[ord(P[2])]; @@ -55180,7 +54817,7 @@ function JSONArrayDecode(P: PUTF8Char; out Values: TPUTF8CharDynArray): boolean; if P^<>']' then repeat if max=n then begin - inc(max,max shr 3+16); + max := NextGrow(max); SetLength(Values,max); end; Values[n] := P; @@ -55643,141 +55280,6 @@ function JSONReformatToFile(const JSON: RawUTF8; const Dest: TFileName; end; -{ TSynPersistentWithPassword } - -destructor TSynPersistentWithPassword.Destroy; -begin - UniqueRawUTF8(fPassword); - FillZero(fPassword); - inherited Destroy; -end; - -class function TSynPersistentWithPassword.ComputePassword(const PlainPassword: RawUTF8; - CustomKey: cardinal): RawUTF8; -var instance: TSynPersistentWithPassword; -begin - instance := TSynPersistentWithPassword.Create; - try - instance.Key := CustomKey; - instance.SetPassWordPlain(PlainPassword); - result := instance.fPassWord; - finally - instance.Free; - end; -end; - -class function TSynPersistentWithPassword.ComputePassword(PlainPassword: pointer; - PlainPasswordLen: integer; CustomKey: cardinal): RawUTF8; -begin - result := ComputePassword(BinToBase64uri(PlainPassword,PlainPasswordLen)); -end; - -class function TSynPersistentWithPassword.ComputePlainPassword(const CypheredPassword: RawUTF8; - CustomKey: cardinal; const AppSecret: RawUTF8): RawUTF8; -var instance: TSynPersistentWithPassword; -begin - instance := TSynPersistentWithPassword.Create; - try - instance.Key := CustomKey; - instance.fPassWord := CypheredPassword; - result := instance.GetPassWordPlainInternal(AppSecret); - finally - instance.Free; - end; -end; - -function TSynPersistentWithPassword.GetPasswordFieldAddress: pointer; -begin - result := @fPassword; -end; - -function TSynPersistentWithPassword.GetKey: cardinal; -begin - if self=nil then - result := 0 else - result := fKey xor $A5abba5A; -end; - -function TSynPersistentWithPassword.GetPassWordPlain: RawUTF8; -begin - result := GetPassWordPlainInternal(''); -end; - -function TSynPersistentWithPassword.GetPassWordPlainInternal(AppSecret: RawUTF8): RawUTF8; -var value,pass: RawByteString; - usr: RawUTF8; - i,j: integer; -begin - result := ''; - if (self=nil) or (fPassWord='') then - exit; - if Assigned(TSynPersistentWithPasswordUserCrypt) then begin - if AppSecret='' then - ToText(ClassType,AppSecret); - usr := ExeVersion.User+':'; - i := PosEx(usr,fPassword); - if (i=1) or ((i>0) and (fPassword[i-1]=',')) then begin - inc(i,length(usr)); - j := PosEx(',',fPassword,i); - if j=0 then - j := length(fPassword)+1; - Base64ToBin(@fPassword[i],j-i,pass); - if pass<>'' then - result := TSynPersistentWithPasswordUserCrypt(pass,AppSecret,false); - end else begin - i := PosEx(':',fPassword); - if i>0 then - raise ESynException.CreateUTF8('%.GetPassWordPlain unable to retrieve the '+ - 'stored value: current user is "%", but password in % was encoded for "%"', - [self,ExeVersion.User,AppSecret,copy(fPassword,1,i-1)]); - end; - end; - if result='' then begin - value := Base64ToBin(fPassWord); - SymmetricEncrypt(GetKey,value); - result := value; - end; -end; - -procedure TSynPersistentWithPassword.SetPassWordPlain(const value: RawUTF8); -var tmp: RawByteString; -begin - if self=nil then - exit; - if value='' then begin - fPassWord := ''; - exit; - end; - SetString(tmp,PAnsiChar(value),Length(value)); // private copy - SymmetricEncrypt(GetKey,tmp); - fPassWord := BinToBase64(tmp); -end; - - -{ TSynConnectionDefinition } - -constructor TSynConnectionDefinition.CreateFromJSON(const JSON: RawUTF8; - Key: cardinal); -var privateCopy: RawUTF8; - values: array[0..4] of TValuePUTF8Char; -begin - fKey := Key; - privateCopy := JSON; - JSONDecode(privateCopy,['Kind','ServerName','DatabaseName','User','Password'],@values); - fKind := values[0].ToString; - values[1].ToUTF8(fServerName); - values[2].ToUTF8(fDatabaseName); - values[3].ToUTF8(fUser); - values[4].ToUTF8(fPassWord); -end; - -function TSynConnectionDefinition.SaveToJSON: RawUTF8; -begin - result := JSONEncode(['Kind',fKind,'ServerName',fServerName, - 'DatabaseName',fDatabaseName,'User',fUser,'Password',fPassword]); -end; - - { ************ some console functions } var @@ -55876,9 +55378,8 @@ procedure TextColor(Color: TConsoleColor); exit; TextAttr := ord(color); if ord(color)>=8 then - write(#27'[1;3') else - write(#27'[0;3'); - write(AnsiTbl[(ord(color) and 7)+1],'m'); + write(#27'[1;3',AnsiTbl[(ord(color) and 7)+1],'m') else + write(#27'[0;3',AnsiTbl[(ord(color) and 7)+1],'m'); ioresult; end; {$I+} @@ -56119,40 +55620,43 @@ function TCommandLine.AsString(const Switch: RawUTF8; const Default, Prompt: str { ************ Unit-Testing classes and functions } -procedure KB(bytes: Int64; out result: TShort16); -const _B: array[0..5] of string[3] = (' KB',' MB',' GB',' TB',' PB',' EB'); -var hi,rem,b: cardinal; +procedure KB(bytes: Int64; out result: TShort16; nospace: boolean); +type TUnits = (kb,mb,gb,tb,pb,eb,b); +const TXT: array[boolean,TUnits] of RawUTF8 = + ((' KB',' MB',' GB',' TB',' PB',' EB','% B'), ('KB','MB','GB','TB','PB','EB','%B')); +var hi,rem: cardinal; + u: TUnits; begin if bytes<1 shl 10-(1 shl 10) div 10 then begin - FormatShort16('% B',[integer(bytes)],result); + FormatShort16(TXT[nospace,b],[integer(bytes)],result); exit; end; if bytes<1 shl 20-(1 shl 20) div 10 then begin - b := 0; + u := kb; rem := bytes; hi := bytes shr 10; end else if bytes<1 shl 30-(1 shl 30) div 10 then begin - b := 1; + u := mb; rem := bytes shr 10; hi := bytes shr 20; end else if bytes0 then - FormatShort16('%.%%',[hi,rem,_B[b]],result) else - FormatShort16('%%',[hi,_B[b]],result); + FormatShort16('%.%%',[hi,rem,TXT[nospace,u]],result) else + FormatShort16('%%',[hi,TXT[nospace,u]],result); end; function KB(bytes: Int64): TShort16; begin - KB(bytes,result); + KB(bytes,result,{nospace=}false); +end; + +function KBNoSpace(bytes: Int64): TShort16; +begin + KB(bytes,result,{nospace=}true); +end; + +function KB(bytes: Int64; nospace: boolean): TShort16; +begin + KB(bytes,result,nospace); end; function KB(const buffer: RawByteString): TShort16; begin - KB(length(buffer), result); + KB(length(buffer),result,{nospace=}false); end; procedure KBU(bytes: Int64; var result: RawUTF8); var tmp: TShort16; begin - KB(bytes,tmp); + KB(bytes,tmp,{nospace=}false); FastSetString(result,@tmp[1],ord(tmp[0])); end; -function IntToThousandString(Value: integer; const ThousandSep: RawUTF8): RawUTF8; +function IntToThousandString(Value: integer; const ThousandSep: TShort4): shortstring; var i,L,Len: cardinal; begin - Int32ToUtf8(value,result); - L := length(Result); + str(Value,result); + L := length(result); Len := L+1; if Value<0 then dec(L,2) else // ignore '-' sign dec(L); for i := 1 to L div 3 do - insert(ThousandSep,Result,Len-i*3); + insert(ThousandSep,result,Len-i*3); end; function MicroSecToString(Micro: QWord): TShort16; @@ -56227,12 +55741,14 @@ procedure MicroSecToString(Micro: QWord; out result: TShort16); if Micro<1000 then FormatShort16('%us',[Micro],result) else if Micro<1000000 then - TwoDigitToString({$ifdef CPU32}Int64Rec(Micro).Lo{$else}Micro{$endif} div 10,'ms',result) else + TwoDigitToString({$ifdef CPU32}PCardinal(@Micro)^{$else}Micro{$endif} div 10,'ms',result) else if Micro<60000000 then - TwoDigitToString({$ifdef CPU32}Int64Rec(Micro).Lo{$else}Micro{$endif} div 10000,'s',result) else - if Micro<3600000000 then - TimeToString({$ifdef CPU32}Int64Rec(Micro).Lo{$else}Micro{$endif} div 1000000,'m',result) else - TimeToString(Micro div 60000000,'h',result); + TwoDigitToString({$ifdef CPU32}PCardinal(@Micro)^{$else}Micro{$endif} div 10000,'s',result) else + if Micro 0; + result := fStart <> 0; end; procedure TPrecisionTimer.ComputeTime; begin - QueryPerformanceCounter(iStop); - if iFreq=0 then begin - QueryPerformanceFrequency(iFreq); - if iFreq=0 then begin - iTime := 0; - iLastTime := 0; + {$ifdef LINUX} + QueryPerformanceMicroSeconds(fStop); + fTime := fStop-fStart; + fLastTime := fStop-fLast; + {$else} + QueryPerformanceCounter(fStop); + if fWinFreq=0 then begin + QueryPerformanceFrequency(fWinFreq); + if fWinFreq=0 then begin + fTime := 0; + fLastTime := 0; exit; end; end; - iTime := ((iStop-iStart)*Int64(1000*1000))div iFreq; - iLastTime := ((iStop-iLast)*Int64(1000*1000))div iFreq; + {$ifdef DELPHI5OROLDER} // circumvent C1093 Error + fTime := ((fStop-fStart)*1000000) div fWinFreq; + if fLast=fStart then + fLastTime := fTime else + fLastTime := ((fStop-fLast)*1000000) div fWinFreq; + {$else} + fTime := (QWord(fStop-fStart)*QWord(1000000)) div QWord(fWinFreq); + if fLast=fStart then + fLastTime := fTime else + fLastTime := (QWord(fStop-fLast)*QWord(1000000)) div QWord(fWinFreq); + {$endif DELPHI5OROLDER} + {$endif LINUX} end; procedure TPrecisionTimer.FromExternalMicroSeconds(const MicroSeconds: QWord); begin - iLastTime := MicroSeconds; - inc(iTime,MicroSeconds); + fLastTime := MicroSeconds; + inc(fTime,MicroSeconds); end; function TPrecisionTimer.FromExternalQueryPerformanceCounters(const CounterDiff: QWord): QWord; -begin // very close to ComputeTime - if iFreq=0 then begin - iTime := 0; - QueryPerformanceFrequency(iFreq); +begin // mimics ComputeTime from already known elapsed time + {$ifdef LINUX} + FromExternalMicroSeconds(CounterDiff); + {$else} + if fWinFreq=0 then begin + fTime := 0; + fLastTime := 0; + QueryPerformanceFrequency(fWinFreq); end; - if iFreq=0 then - iLastTime := 0 else - FromExternalMicroSeconds((Int64(CounterDiff)*Int64(1000*1000))div iFreq); - result := iLastTime; + if fWinFreq<>0 then + FromExternalMicroSeconds((CounterDiff*QWord(1000000))div PQWord(@fWinFreq)^); + {$endif LINUX} + result := fLastTime; end; function TPrecisionTimer.Stop: TShort16; begin ComputeTime; - MicroSecToString(iTime,result); + MicroSecToString(fTime,result); end; procedure TPrecisionTimer.Pause; begin - QueryPerformanceCounter(iResume); - dec(iResume,iStart); + {$ifdef LINUX}QueryPerformanceMicroSeconds{$else}QueryPerformanceCounter{$endif}(fResume); + dec(fResume,fStart); inc(fPauseCount); end; procedure TPrecisionTimer.Resume; begin - QueryPerformanceCounter(iStart); - iLast := iStart; - dec(iStart,iResume); - iResume := 0; + {$ifdef LINUX}QueryPerformanceMicroSeconds{$else}QueryPerformanceCounter{$endif}(fStart); + fLast := fStart; + dec(fStart,fResume); + fResume := 0; end; function TPrecisionTimer.Time: TShort16; begin - MicroSecToString(iTime,result); + MicroSecToString(fTime,result); end; function TPrecisionTimer.LastTime: TShort16; begin - MicroSecToString(iLastTime,result); + MicroSecToString(fLastTime,result); end; @@ -56381,7 +55921,7 @@ destructor TPrecisionTimerProfiler.Destroy; function TPrecisionTimer.ProfileCurrentMethod: IUnknown; begin - if iStart=0 then + if fStart=0 then Start else Resume; result := TPrecisionTimerProfiler.Create(@self); @@ -56435,9 +55975,9 @@ function TSynMonitorTime.GetAsText: TShort16; function TSynMonitorTime.PerSecond(const Count: QWord): QWord; begin - if PInt64(@fMicroSeconds)^<=0 then // avoid negative or div per 0 - result := 0 else - result := (Count*QWord(1000*1000)) div fMicroSeconds; + if {$ifdef FPC}Int64(fMicroSeconds){$else}PInt64(@fMicroSeconds)^{$endif}<=0 then + result := 0 else // avoid negative or div per 0 + result := (Count*QWord(1000000)) div fMicroSeconds; end; @@ -56450,31 +55990,39 @@ function TSynMonitorOneTime.GetAsText: TShort16; function TSynMonitorOneTime.PerSecond(const Count: QWord): QWord; begin - if PInt64(@fMicroSeconds)^<=0 then // avoid negative or div per 0 + if {$ifdef FPC}Int64(fMicroSeconds){$else}PInt64(@fMicroSeconds)^{$endif}<=0 then result := 0 else - result := (Count*QWord(1000*1000)) div fMicroSeconds; + result := (Count*QWord(1000000)) div fMicroSeconds; end; +{ TSynMonitorSizeParent } + +constructor TSynMonitorSizeParent.Create(aTextNoSpace: boolean); +begin + inherited Create; + fTextNoSpace := aTextNoSpace; +end; + { TSynMonitorSize } function TSynMonitorSize.GetAsText: TShort16; begin - KB(fBytes,result); + KB(fBytes,result,fTextNoSpace); end; { TSynMonitorOneSize } function TSynMonitorOneSize.GetAsText: TShort16; begin - KB(fBytes,result); + KB(fBytes,result,fTextNoSpace); end; { TSynMonitorThroughput } function TSynMonitorThroughput.GetAsText: TShort16; begin - FormatShort16('%/s',[KB(fBytesPerSec)],result); + FormatShort16('%/s',[KB(fBytesPerSec,fTextNoSpace)],result); end; @@ -56488,7 +56036,6 @@ constructor TSynMonitor.Create; fMinimalTime := TSynMonitorOneTime.Create; fAverageTime := TSynMonitorOneTime.Create; fMaximalTime := TSynMonitorOneTime.Create; - InitializeCriticalSection(fLock); end; constructor TSynMonitor.Create(const aName: RawUTF8); @@ -56504,18 +56051,17 @@ destructor TSynMonitor.Destroy; fMinimalTime.Free; fLastTime.Free; fTotalTime.Free; - DeleteCriticalSection(fLock); inherited Destroy; end; procedure TSynMonitor.Lock; begin - EnterCriticalSection(fLock); + fSafe^.Lock; end; procedure TSynMonitor.UnLock; begin - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; procedure TSynMonitor.Changed; @@ -56526,25 +56072,25 @@ procedure TSynMonitor.ProcessStart; begin if fProcessing then raise ESynException.CreateUTF8('Reentrant %.ProcessStart',[self]); - EnterCriticalSection(fLock); + fSafe^.Lock; try InternalTimer.Resume; fTaskStatus := taskNotStarted; fProcessing := true; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; procedure TSynMonitor.ProcessDoTask; begin - EnterCriticalSection(fLock); + fSafe^.Lock; try inc(fTaskCount); fTaskStatus := taskStarted; Changed; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56552,7 +56098,7 @@ procedure TSynMonitor.ProcessStartTask; begin if fProcessing then raise ESynException.CreateUTF8('Reentrant %.ProcessStart',[self]); - EnterCriticalSection(fLock); + fSafe^.Lock; try InternalTimer.Resume; fProcessing := true; @@ -56560,19 +56106,19 @@ procedure TSynMonitor.ProcessStartTask; fTaskStatus := taskStarted; Changed; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; procedure TSynMonitor.ProcessEnd; begin - EnterCriticalSection(fLock); + fSafe^.Lock; try InternalTimer.Pause; InternalTimer.ComputeTime; LockedFromProcessTimer; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56595,27 +56141,27 @@ procedure TSynMonitor.LockedFromProcessTimer; function TSynMonitor.FromExternalQueryPerformanceCounters(const CounterDiff: QWord): QWord; begin - EnterCriticalSection(fLock); + fSafe^.Lock; try // thread-safe ProcessStart+ProcessDoTask+ProcessEnd inc(fTaskCount); fTaskStatus := taskStarted; result := InternalTimer.FromExternalQueryPerformanceCounters(CounterDiff); LockedFromProcessTimer; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; procedure TSynMonitor.FromExternalMicroSeconds(const MicroSecondsElapsed: QWord); begin - EnterCriticalSection(fLock); + fSafe^.Lock; try // thread-safe ProcessStart+ProcessDoTask+ProcessEnd inc(fTaskCount); fTaskStatus := taskStarted; InternalTimer.FromExternalMicroSeconds(MicroSecondsElapsed); LockedFromProcessTimer; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56630,14 +56176,14 @@ class procedure TSynMonitor.InitializeObjArray(var ObjArr; Count: integer); procedure TSynMonitor.ProcessError(const info: variant); begin - EnterCriticalSection(fLock); + fSafe^.Lock; try if not VarIsEmptyOrNull(info) then inc(fInternalErrors); fLastInternalError := info; Changed; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56663,20 +56209,20 @@ procedure TSynMonitor.LockedPerSecProperties; if fTaskCount=0 then exit; // avoid division per zero fPerSec := fTotalTime.PerSecond(fTaskCount); - fAverageTime.MicroSec := Round(fTotalTime.MicroSec/fTaskCount); + fAverageTime.MicroSec := fTotalTime.MicroSec div fTaskCount; end; procedure TSynMonitor.Sum(another: TSynMonitor); begin if (self=nil) or (another=nil) then exit; - EnterCriticalSection(fLock); - EnterCriticalSection(another.fLock); + fSafe^.Lock; + another.fSafe^.Lock; try LockedSum(another); finally - LeaveCriticalSection(another.fLock); - LeaveCriticalSection(fLock); + another.fSafe^.UnLock; + fSafe^.UnLock; end; end; @@ -56696,22 +56242,22 @@ procedure TSynMonitor.LockedSum(another: TSynMonitor); procedure TSynMonitor.WriteDetailsTo(W: TTextWriter); begin - EnterCriticalSection(fLock); + fSafe^.Lock; try W.WriteObject(self); finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; procedure TSynMonitor.ComputeDetailsTo(W: TTextWriter); begin - EnterCriticalSection(fLock); + fSafe^.Lock; try LockedPerSecProperties; // may not have been calculated after Sum() WriteDetailsTo(W); finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56741,8 +56287,8 @@ function TSynMonitor.ComputeDetails: variant; constructor TSynMonitorWithSize.Create; begin inherited Create; - fSize := TSynMonitorSize.Create; - fThroughput := TSynMonitorThroughput.Create; + fSize := TSynMonitorSize.Create({nospace=}false); + fThroughput := TSynMonitorThroughput.Create({nospace=}false); end; destructor TSynMonitorWithSize.Destroy; @@ -56760,11 +56306,11 @@ procedure TSynMonitorWithSize.LockedPerSecProperties; procedure TSynMonitorWithSize.AddSize(const Bytes: QWord); begin - EnterCriticalSection(fLock); + fSafe^.Lock; try fSize.Bytes := fSize.Bytes+Bytes; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56781,10 +56327,10 @@ procedure TSynMonitorWithSize.LockedSum(another: TSynMonitor); constructor TSynMonitorInputOutput.Create; begin inherited Create; - fInput := TSynMonitorSize.Create; - fOutput := TSynMonitorSize.Create; - fInputThroughput := TSynMonitorThroughput.Create; - fOutputThroughput := TSynMonitorThroughput.Create; + fInput := TSynMonitorSize.Create({nospace=}false); + fOutput := TSynMonitorSize.Create({nospace=}false); + fInputThroughput := TSynMonitorThroughput.Create({nospace=}false); + fOutputThroughput := TSynMonitorThroughput.Create({nospace=}false); end; destructor TSynMonitorInputOutput.Destroy; @@ -56805,12 +56351,12 @@ procedure TSynMonitorInputOutput.LockedPerSecProperties; procedure TSynMonitorInputOutput.AddSize(const Incoming, Outgoing: QWord); begin - EnterCriticalSection(fLock); + fSafe^.Lock; try fInput.Bytes := fInput.Bytes+Incoming; fOutput.Bytes := fOutput.Bytes+Outgoing; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56830,14 +56376,14 @@ procedure TSynMonitorServer.ClientConnect; begin if self=nil then exit; - EnterCriticalSection(fLock); + fSafe^.Lock; try inc(fClientsCurrent); if fClientsCurrent>fClientsMax then fClientsMax := fClientsCurrent; Changed; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56845,13 +56391,13 @@ procedure TSynMonitorServer.ClientDisconnect; begin if self=nil then exit; - EnterCriticalSection(fLock); + fSafe^.Lock; try if fClientsCurrent>0 then dec(fClientsCurrent); Changed; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56859,12 +56405,12 @@ procedure TSynMonitorServer.ClientDisconnectAll; begin if self=nil then exit; - EnterCriticalSection(fLock); + fSafe^.Lock; try fClientsCurrent := 0; Changed; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56874,11 +56420,11 @@ function TSynMonitorServer.GetClientsCurrent: TSynMonitorOneCount; result := 0; exit; end; - EnterCriticalSection(fLock); + fSafe^.Lock; try result := fClientsCurrent; finally - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; @@ -56888,375 +56434,16 @@ function TSynMonitorServer.AddCurrentRequestCount(diff: integer): integer; result := 0; exit; end; - EnterCriticalSection(fLock); + fSafe^.Lock; try inc(fCurrentRequestCount,diff); result := fCurrentRequestCount; finally - LeaveCriticalSection(fLock); - end; -end; - -{ TSynMonitorMemory } - -constructor TSynMonitorMemory.Create; -begin - FAllocatedUsed := TSynMonitorOneSize.create; - FAllocatedReserved := TSynMonitorOneSize.create; - FPhysicalMemoryFree := TSynMonitorOneSize.Create; - FVirtualMemoryFree := TSynMonitorOneSize.Create; - FPagingFileTotal := TSynMonitorOneSize.Create; - FPhysicalMemoryTotal := TSynMonitorOneSize.Create; - FVirtualMemoryTotal := TSynMonitorOneSize.Create; - FPagingFileFree := TSynMonitorOneSize.Create; -end; - -destructor TSynMonitorMemory.Destroy; -begin - FAllocatedReserved.Free; - FAllocatedUsed.Free; - FPhysicalMemoryFree.Free; - FVirtualMemoryFree.Free; - FPagingFileTotal.Free; - FPhysicalMemoryTotal.Free; - FVirtualMemoryTotal.Free; - FPagingFileFree.Free; - inherited Destroy; -end; - -class function TSynMonitorMemory.FreeAsText: RawUTF8; -begin - with TSynMonitorMemory.Create do - try - RetrieveMemoryInfo; - FormatUTF8('% / %',[fPhysicalMemoryFree.Text,fPhysicalMemoryTotal.Text],result); - finally - Free; - end; -end; - -var - PhysicalAsTextCache: RawUTF8; // this value doesn't change usually - -class function TSynMonitorMemory.PhysicalAsText: RawUTF8; -begin - if PhysicalAsTextCache='' then - with TSynMonitorMemory.Create do - try - PhysicalAsTextCache := PhysicalMemoryTotal.Text; - finally - Free; - end; - result := PhysicalAsTextCache; -end; - -class function TSynMonitorMemory.ToJSON: RawUTF8; -begin - with TSynMonitorMemory.Create do - try - RetrieveMemoryInfo; - FormatUTF8('{Allocated:{reserved:%,used:%},Physical:{total:%,free:%,percent:%},'+ - {$ifdef MSWINDOWS}'Virtual:{total:%,free:%},'+{$endif}'Paged:{total:%,free:%}}', - [fAllocatedReserved.Bytes shr 10,fAllocatedUsed.Bytes shr 10, - fPhysicalMemoryTotal.Bytes shr 10,fPhysicalMemoryFree.Bytes shr 10, fMemoryLoadPercent, - {$ifdef MSWINDOWS}fVirtualMemoryTotal.Bytes shr 10,fVirtualMemoryFree.Bytes shr 10,{$endif} - fPagingFileTotal.Bytes shr 10,fPagingFileFree.Bytes shr 10],result); - finally - Free; - end; -end; - -{$ifndef NOVARIANTS} -class function TSynMonitorMemory.ToVariant: variant; -begin - result := _JsonFast(ToJSON); -end; -{$endif} - -function TSynMonitorMemory.GetAllocatedUsed: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FAllocatedUsed; -end; - -function TSynMonitorMemory.GetAllocatedReserved: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FAllocatedReserved; -end; - -function TSynMonitorMemory.GetMemoryLoadPercent: integer; -begin - RetrieveMemoryInfo; - result := FMemoryLoadPercent; -end; - -function TSynMonitorMemory.GetPagingFileFree: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FPagingFileFree; -end; - -function TSynMonitorMemory.GetPagingFileTotal: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FPagingFileTotal; -end; - -function TSynMonitorMemory.GetPhysicalMemoryFree: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FPhysicalMemoryFree; -end; - -function TSynMonitorMemory.GetPhysicalMemoryTotal: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FPhysicalMemoryTotal; -end; - -function TSynMonitorMemory.GetVirtualMemoryFree: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FVirtualMemoryFree; -end; - -function TSynMonitorMemory.GetVirtualMemoryTotal: TSynMonitorOneSize; -begin - RetrieveMemoryInfo; - result := FVirtualMemoryTotal; -end; - -{$ifdef MSWINDOWS} -{$ifndef UNICODE} // missing API for oldest Delphi -type - DWORDLONG = Int64; - TMemoryStatusEx = record - dwLength: DWORD; - dwMemoryLoad: DWORD; - ullTotalPhys: DWORDLONG; - ullAvailPhys: DWORDLONG; - ullTotalPageFile: DWORDLONG; - ullAvailPageFile: DWORDLONG; - ullTotalVirtual: DWORDLONG; - ullAvailVirtual: DWORDLONG; - ullAvailExtendedVirtual: DWORDLONG; - end; - -// information about the system's current usage of both physical and virtual memory -function GlobalMemoryStatusEx(var lpBuffer: TMemoryStatusEx): BOOL; - stdcall; external kernel32; -{$endif} -{$endif} - -procedure TSynMonitorMemory.RetrieveMemoryInfo; -procedure RetrieveInfo; -{$ifndef FPC} -var Heap: TMemoryManagerState; - sb: integer; - tot,res: QWord; -{$endif} -{$ifdef MSWINDOWS} -var global: TMemoryStatusEx; - {$ifdef FPC}mem: TProcessMemoryCounters;{$endif} -begin - FillCharFast(global,SizeOf(global),0); - global.dwLength := SizeOf(global); - GlobalMemoryStatusEx(global); - FMemoryLoadPercent := global.dwMemoryLoad; - FPhysicalMemoryTotal.fBytes := global.ullTotalPhys; - FPhysicalMemoryFree.fBytes := global.ullAvailPhys; - FPagingFileTotal.fBytes := global.ullTotalPageFile; - FPagingFileFree.fBytes := global.ullAvailPageFile; - FVirtualMemoryTotal.fBytes := global.ullTotalVirtual; - FVirtualMemoryFree.fBytes := global.ullAvailVirtual; - {$ifdef FPC} // GetHeapStatus is only about current thread -> use WinAPI - if Assigned(GetProcessMemoryInfo) then begin - FillcharFast(mem,SizeOf(mem),0); - mem.cb := SizeOf(mem); - GetProcessMemoryInfo(GetCurrentProcess,mem,SizeOf(mem)); - FAllocatedReserved.fBytes := mem.PeakWorkingSetSize; - FAllocatedUsed.fBytes := mem.WorkingSetSize; - end; - {$endif FPC} -{$else} -{$ifdef BSD} -begin - FPhysicalMemoryTotal.fBytes := fpsysctlhwint( - {$ifdef DARWIN}HW_MEMSIZE{$else}HW_PHYSMEM{$endif}); - FPhysicalMemoryFree.fBytes := FPhysicalMemoryTotal.fBytes-fpsysctlhwint(HW_USERMEM); - if FPhysicalMemoryTotal.fBytes<>0 then // avoid div per 0 exception - FMemoryLoadPercent := ((FPhysicalMemoryTotal.fBytes-FPhysicalMemoryFree.fBytes)*100)div FPhysicalMemoryTotal.fBytes; -{$else} -var si: TSysInfo; // Linuxism - P: PUTF8Char; - {$ifdef FPC}mu: cardinal{$else}const mu=1{$endif}; -begin - {$ifdef FPC} - SysInfo(@si); - mu := si.mem_unit; - {$else} - SysInfo(si); // missing field in Kylix' Libc - {$endif} - if si.totalram<>0 then // avoid div per 0 exception - FMemoryLoadPercent := ((si.totalram-si.freeram)*100)div si.totalram; - FPhysicalMemoryTotal.fBytes := si.totalram*mu; - FPhysicalMemoryFree.fBytes := si.freeram*mu; - FPagingFileTotal.fBytes := si.totalswap*mu; - FPagingFileFree.fBytes := si.freeswap*mu; - // virtual memory information is not available under Linux - P := pointer(StringFromFile('/proc/self/statm',true)); - FAllocatedReserved.fBytes := GetNextItemCardinal(P,' ')*SystemInfo.dwPageSize; // VmSize - FAllocatedUsed.fBytes := GetNextItemCardinal(P,' ')*SystemInfo.dwPageSize; // VmRSS - // GetHeapStatus is only about current thread -> use /proc/[pid]/statm -{$endif BSD} -{$endif MSWINDOWS} -{$ifndef FPC} -{$ifdef LVCL} - tot := 0; - res := 0; -{$else} - GetMemoryManagerState(Heap); // direct access to FastMM4 statistics - tot := Heap.TotalAllocatedMediumBlockSize+Heap.TotalAllocatedLargeBlockSize; - res := Heap.ReservedMediumBlockAddressSpace+Heap.ReservedLargeBlockAddressSpace; - for sb := 0 to high(Heap.SmallBlockTypeStates) do - with Heap.SmallBlockTypeStates[sb] do begin - inc(tot,UseableBlockSize*AllocatedBlockCount); - inc(res,ReservedAddressSpace); - end; -{$endif LVCL} - FAllocatedUsed.fBytes := tot; - FAllocatedReserved.fBytes := res; -{$endif FPC} -end; -var tix: cardinal; -begin - tix := GetTickCount64 shr 7; // allow 128 ms resolution for updates - if fLastMemoryInfoRetrievedTix<>tix then begin - fLastMemoryInfoRetrievedTix := tix; - RetrieveInfo; - end; -end; - - -{ TSynMonitorDisk } - -constructor TSynMonitorDisk.Create; -begin - fAvailableSize := TSynMonitorOneSize.Create; - fFreeSize := TSynMonitorOneSize.Create; - fTotalSize := TSynMonitorOneSize.Create; -end; - -destructor TSynMonitorDisk.Destroy; -begin - fAvailableSize.Free; - fFreeSize.Free; - fTotalSize.Free; - inherited; -end; - -function TSynMonitorDisk.GetName: TFileName; -begin - RetrieveDiskInfo; - result := fName; -end; - -function TSynMonitorDisk.GetAvailable: TSynMonitorOneSize; -begin - RetrieveDiskInfo; - result := fAvailableSize; -end; - -function TSynMonitorDisk.GetFree: TSynMonitorOneSize; -begin - RetrieveDiskInfo; - result := fFreeSize; -end; - -function TSynMonitorDisk.GetTotal: TSynMonitorOneSize; -begin - RetrieveDiskInfo; - result := fTotalSize; -end; - -class function TSynMonitorDisk.FreeAsText: RawUTF8; -var name: TFileName; - avail,free,total: QWord; -begin - GetDiskInfo(name,avail,free,total); - FormatUTF8('% % / %',[name, KB(free),KB(total)],result); -end; - -{$ifdef MSWINDOWS} -function GetDiskFreeSpaceExA(lpDirectoryName: PAnsiChar; - var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes, - lpTotalNumberOfFreeBytes: QWord): LongBool; stdcall; external kernel32; -function GetDiskFreeSpaceExW(lpDirectoryName: PWideChar; - var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes, - lpTotalNumberOfFreeBytes: QWord): LongBool; stdcall; external kernel32; -{$endif} - -procedure GetDiskInfo(var aDriveFolderOrFile: TFileName; - out aAvailableBytes, aFreeBytes, aTotalBytes: QWord - {$ifdef MSWINDOWS}; aVolumeName: PFileName = nil{$endif}); -{$ifdef MSWINDOWS} -var tmp: array[0..MAX_PATH-1] of Char; - dummy,flags: DWORD; - dn: TFileName; -begin - if aDriveFolderOrFile='' then - aDriveFolderOrFile := SysUtils.UpperCase(ExtractFileDrive(ExeVersion.ProgramFilePath)); - dn := aDriveFolderOrFile; - if (dn<>'') and (dn[2]=':') and (dn[3]=#0) then - dn := dn+'\'; - if (aVolumeName<>nil) and (aVolumeName^='') then begin - tmp[0] := #0; - GetVolumeInformation(pointer(dn),tmp,MAX_PATH,nil,dummy,flags,nil,0); - aVolumeName^ := tmp; - end; - {$ifdef UNICODE}GetDiskFreeSpaceExW{$else}GetDiskFreeSpaceExA{$endif}( - pointer(dn),aAvailableBytes,aTotalBytes,aFreeBytes); -{$else} -{$ifdef KYLIX3} -var fs: TStatFs64; - h: THandle; -begin - if aDriveFolderOrFile='' then - aDriveFolderOrFile := '.'; - h := FileOpen(aDriveFolderOrFile,fmShareDenyNone); - fstatfs64(h,fs); - FileClose(h); - aAvailableBytes := fs.f_bavail*fs.f_bsize; - aFreeBytes := aAvailableBytes; - aTotalBytes := fs.f_blocks*fs.f_bsize; -{$endif} -{$ifdef FPC} -var fs: tstatfs; -begin - if aDriveFolderOrFile='' then - aDriveFolderOrFile := '.'; - fpStatFS(aDriveFolderOrFile,@fs); - aAvailableBytes := QWord(fs.bavail)*QWord(fs.bsize); - aFreeBytes := aAvailableBytes; // no user Quota involved here - aTotalBytes := QWord(fs.blocks)*QWord(fs.bsize); -{$endif FPC} -{$endif MSWINDOWS} -end; - -procedure TSynMonitorDisk.RetrieveDiskInfo; -var tix: cardinal; -begin - tix := GetTickCount64 shr 7; // allow 128 ms resolution for updates - if fLastDiskInfoRetrievedTix<>tix then begin - fLastDiskInfoRetrievedTix := tix; - GetDiskInfo(fName,PQWord(@fAvailableSize.fBytes)^,PQWord(@fFreeSize.fBytes)^, - PQWord(@fTotalSize.fBytes)^{$ifdef MSWINDOWS},@fVolumeName{$endif}); + fSafe^.UnLock; end; end; - { ******************* cross-cutting classes and functions ***************** } { TSynInterfacedObject } @@ -57672,7 +56859,7 @@ function GetDelphiCompilerVersion: RawUTF8; {$ifdef VER3_0_1}+' 3.0.1'{$endif} {$ifdef VER3_0_2}+' 3.0.2'{$endif} {$ifdef VER3_1_1}+' 3.1.1'{$endif} - {$ifdef VER3_2}+' 3.2'{$endif} + {$ifdef VER3_2} +' 3.2' {$endif} {$ifdef VER3_3_1}+' 3.3.1'{$endif} {$else} {$ifdef VER130} 'Delphi 5'{$endif} @@ -57712,18 +56899,12 @@ function GetDelphiCompilerVersion: RawUTF8; constructor TSynCache.Create(aMaxCacheRamUsed: cardinal; aCaseSensitive: boolean; aTimeoutSeconds: cardinal); begin + inherited Create; fNameValue.Init(aCaseSensitive); fNameValue.fDynArray.Capacity := 200; // some space for future cached entries fMaxRamUsed := aMaxCacheRamUsed; fFindLastAddedIndex := -1; fTimeoutSeconds := aTimeoutSeconds; - fSafe.Init; -end; - -destructor TSynCache.Destroy; -begin - inherited Destroy; - fSafe.Done; end; procedure TSynCache.ResetIfNeeded; @@ -57732,7 +56913,7 @@ procedure TSynCache.ResetIfNeeded; if fRamUsed>fMaxRamUsed then Reset; if fTimeoutSeconds>0 then begin - tix := GetTickCount64 shr 10; + tix := {$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 shr 10; if fTimeoutTix>tix then Reset; fTimeoutTix := tix+fTimeoutSeconds; @@ -57851,10 +57032,8 @@ function TRawUTF8List.Add(const aText: RawUTF8): PtrInt; if fObjects=nil then begin capacity := length(fList); result := fCount; - if result>=capacity then begin - inc(capacity,256+fCount shr 3); - SetLength(fList,capacity); - end; + if result>=capacity then + SetLength(fList,NextGrow(capacity)); fList[result] := aText; inc(fCount); Changed; @@ -57897,13 +57076,13 @@ function TRawUTF8List.AddObject(const aText: RawUTF8; aObject: TObject): PtrInt; capacity := length(fList); result := fCount; if result>=capacity then begin - inc(capacity,256+fCount shr 3); + capacity := NextGrow(capacity); SetLength(fList,capacity); if (fObjects<>nil) or (aObject<>nil) then SetLength(fObjects,capacity); end else if (aObject<>nil) and (fObjects=nil) then - SetLength(fObjects,capacity); + SetLength(fObjects,capacity); // first time we got aObject<>nil fList[result] := aText; if aObject<>nil then fObjects[result] := aObject; @@ -57971,10 +57150,12 @@ procedure TRawUTF8List.Delete(Index: PtrInt); // swap the string/object arrays dec(fCount); if Indexnil then begin - MoveFast(fObjects[Index+1],fObjects[Index],(fCount-Index)*SizeOf(fObjects[0])); + {$ifdef FPC}Move{$else}MoveFast{$endif}( + fObjects[Index+1],fObjects[Index],(fCount-Index)*SizeOf(fObjects[0])); fObjects[fCount] := nil; // avoid GPF if fObjectsOwned is set end; end; @@ -58044,15 +57225,14 @@ function TRawUTF8List.GetObjectPtr: PPointerArray; end; function TRawUTF8List.GetName(Index: PtrInt): RawUTF8; -var Sep: PUTF8Char; begin result := Get(Index); if result='' then exit; - Sep := PosChar(pointer(result),NameValueSep); - if Sep=nil then + Index := PosExChar(NameValueSep,result); + if Index=0 then result := '' else - SetLength(result,Sep-pointer(result)); + SetLength(result,Index-1); end; function TRawUTF8List.GetObject(Index: PtrInt): TObject; @@ -58097,13 +57277,13 @@ function TRawUTF8List.GetText(const Delimiter: RawUTF8): RawUTF8; repeat Len := length(fList[i]); if Len>0 then begin - MoveFast(pointer(fList[i])^,P^,Len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(fList[i])^,P^,Len); inc(P,Len); end; inc(i); if i>=fCount then Break; - MoveFast(pointer(Delimiter)^,P^,DelimLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Delimiter)^,P^,DelimLen); inc(P,DelimLen); until false; end; @@ -58153,17 +57333,16 @@ function TRawUTF8List.GetValue(const Name: RawUTF8): RawUTF8; end; function TRawUTF8List.GetValueAt(Index: PtrInt): RawUTF8; -var Sep: PUTF8Char; begin if (self=nil) or (PtrUInt(Index)>=PtrUInt(fCount)) then result := '' else result := Get(Index); if result='' then exit; - Sep := PosChar(pointer(result),NameValueSep); - if Sep=nil then + Index := PosExChar(NameValueSep,result); + if Index=0 then result := '' else - result := Sep+1; // get 'Value' from 'Name=Value' + result := copy(result,Index+1,maxInt); end; function TRawUTF8List.IndexOf(const aText: RawUTF8): PtrInt; @@ -58939,7 +58118,8 @@ procedure TRawUTF8MethodList.Delete(Index: PtrInt); begin inherited Delete(Index); if Index0 then - result := cardinal(GetTickCount64 shr 10)+result; + result := cardinal({$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 shr 10)+result; end; function TSynDictionary.GetCapacity: integer; @@ -59049,7 +58229,7 @@ function TSynDictionary.GetTimeOutSeconds: cardinal; end; procedure TSynDictionary.SetTimeouts; -var i: integer; +var i: PtrInt; timeout: cardinal; begin if fSafe.Padding[DIC_TIMESEC].VInteger=0 then @@ -59061,14 +58241,14 @@ procedure TSynDictionary.SetTimeouts; end; function TSynDictionary.DeleteDeprecated: integer; -var i: integer; +var i: PtrInt; now: cardinal; begin result := 0; if (self=nil) or (fSafe.Padding[DIC_TIMECOUNT].VInteger=0) or // no entry (fSafe.Padding[DIC_TIMESEC].VInteger=0) then // nothing in fTimeOut[] exit; - now := GetTickCount64 shr 10; + now := {$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 shr 10; if fSafe.Padding[DIC_TIMETIX].VInteger=integer(now) then exit; // no need to search more often than every second fSafe.Lock; @@ -59076,8 +58256,8 @@ function TSynDictionary.DeleteDeprecated: integer; fSafe.Padding[DIC_TIMETIX].VInteger := now; for i := fSafe.Padding[DIC_TIMECOUNT].VInteger-1 downto 0 do if (now>fTimeOut[i]) and (fTimeOut[i]<>0) and - (not Assigned(fOnCanDelete) or fOnCanDelete(fKeys.{$ifdef UNDIRECTDYNARRAY} - InternalDynArray.{$endif}ElemPtr(i)^,fValues.ElemPtr(i)^,i)) then begin + (not Assigned(fOnCanDelete) or + fOnCanDelete(fKeys.ElemPtr(i)^,fValues.ElemPtr(i)^,i)) then begin fKeys.Delete(i); fValues.Delete(i); fTimeOuts.Delete(i); @@ -59218,7 +58398,7 @@ function TSynDictionary.InArray(const aKey, aArrayValue; ndx := fKeys.FindHashed(aKey); if ndx<0 then exit; - nested.Init(fValues.ElemType, fValues.ElemPtr(ndx)^); + nested.Init(fValues.ElemType,fValues.ElemPtr(ndx)^); case aAction of iaFind: result := nested.Find(aArrayValue)>=0; @@ -59241,6 +58421,24 @@ function TSynDictionary.FindInArray(const aKey, aArrayValue): boolean; result := InArray(aKey,aArrayValue,iaFind); end; +function TSynDictionary.FindKeyFromValue(const aValue; out aKey; + aUpdateTimeOut: boolean): boolean; +var ndx: integer; +begin + fSafe.Lock; + try + ndx := fValues.IndexOf(aValue); + result := ndx>=0; + if result then begin + fKeys.ElemCopyAt(ndx,aKey); + if aUpdateTimeOut then + SetTimeoutAtIndex(ndx); + end; + finally + fSafe.UnLock; + end; +end; + function TSynDictionary.DeleteInArray(const aKey, aArrayValue): boolean; begin result := InArray(aKey,aArrayValue,iaFindAndDelete); @@ -59270,7 +58468,7 @@ function TSynDictionary.Find(const aKey; aUpdateTimeOut: boolean): integer; if aUpdateTimeOut and (result>=0) then begin tim := fSafe.Padding[DIC_TIMESEC].VInteger; if tim>0 then // inlined fTimeout[result] := GetTimeout - fTimeout[result] := cardinal(GetTickCount64 shr 10)+tim; + fTimeout[result] := cardinal({$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 shr 10)+tim; end; end; @@ -59292,7 +58490,7 @@ function TSynDictionary.FindValueOrAdd(const aKey; var added: boolean; begin tim := fSafe.Padding[DIC_TIMESEC].VInteger; // inlined tim := GetTimeout if tim<>0 then - tim := cardinal(GetTickCount64 shr 10)+tim; + tim := cardinal({$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 shr 10)+tim; ndx := fKeys.FindHashedForAdding(aKey,added); if added then begin with fKeys{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif} do @@ -59430,7 +58628,7 @@ procedure TSynDictionary.SetTimeoutAtIndex(aIndex: integer); exit; tim := fSafe.Padding[DIC_TIMESEC].VInteger; if tim > 0 then - fTimeOut[aIndex] := cardinal(GetTickCount64 shr 10)+tim; + fTimeOut[aIndex] := cardinal({$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64 shr 10)+tim; end; function TSynDictionary.Count: integer; @@ -59545,7 +58743,7 @@ class function TSynDictionary.OnCanDeleteSynPersistentLock(const aKey, aValue; class function TSynDictionary.OnCanDeleteSynPersistentLocked(const aKey, aValue; aIndex: integer): boolean; begin - result := not TSynPersistentLocked(aValue).Safe.IsLocked; + result := not TSynPersistentLock(aValue).Safe.IsLocked; end; function TSynDictionary.SaveToBinary(NoCompression: boolean): RawByteString; @@ -59583,6 +58781,7 @@ constructor TSynQueue.Create(aTypeInfo: pointer); destructor TSynQueue.Destroy; begin + WaitPopFinalize; fValues.Clear; inherited Destroy; end; @@ -59706,6 +58905,80 @@ function TSynQueue.Pop(out aValue): boolean; end; end; +function TSynQueue.InternalDestroying(incPopCounter: integer): boolean; +begin + fSafe.Lock; + try + result := wpfDestroying in fWaitPopFlags; + inc(fWaitPopCounter, incPopCounter); + finally + fSafe.UnLock; + end; +end; + +function TSynQueue.InternalWaitDone(endtix: Int64; const idle: TThreadMethod): boolean; +begin + Sleep(1); + if Assigned(idle) then + idle; // e.g. Application.ProcessMessages + result := InternalDestroying(0) or (GetTickCount64>endtix); +end; + +function TSynQueue.WaitPop(aTimeoutMS: integer; const aWhenIdle: TThreadMethod; + out aValue): boolean; +var endtix: Int64; +begin + result := false; + if not InternalDestroying(+1) then + try + endtix := GetTickCount64+aTimeoutMS; + repeat + result := Pop(aValue); + until result or InternalWaitDone(endtix,aWhenIdle); + finally + InternalDestroying(-1); + end; +end; + +function TSynQueue.WaitPeekLocked(aTimeoutMS: integer; const aWhenIdle: TThreadMethod): pointer; +var endtix: Int64; +begin + result := nil; + if not InternalDestroying(+1) then + try + endtix := GetTickCount64+aTimeoutMS; + repeat + fSafe.Lock; + try + if fFirst>=0 then + result := fValues.ElemPtr(fFirst); + finally + if result=nil then + fSafe.UnLock; // caller should always Unlock once done + end; + until (result<>nil) or InternalWaitDone(endtix,aWhenIdle); + finally + InternalDestroying(-1); + end; +end; + +procedure TSynQueue.WaitPopFinalize; +var endtix: Int64; // never wait forever +begin + fSafe.Lock; + try + include(fWaitPopFlags,wpfDestroying); + if fWaitPopCounter = 0 then + exit; + finally + fSafe.UnLock; + end; + endtix := GetTickCount64 + 100; + repeat + Sleep(1); // ensure WaitPos() is actually finished + until (fWaitPopCounter=0) or (GetTickCount64>endtix); +end; + procedure TSynQueue.Save(out aDynArrayValues; aDynArray: PDynArray); var n: integer; DA: TDynArray; @@ -59759,11 +59032,11 @@ function TMemoryMap.Map(aFile: THandle; aCustomSize: PtrUInt; aCustomOffset: Int fBufSize := aCustomSize; end; {$ifdef MSWINDOWS} - with Int64Rec(fFileSize) do + with PInt64Rec(@fFileSize)^ do fMap := CreateFileMapping(fFile,nil,PAGE_READONLY,Hi,Lo,nil); if fMap=0 then raise ESynException.Create('TMemoryMap.Map: CreateFileMapping()=0'); - with Int64Rec(aCustomOffset) do + with PInt64Rec(@aCustomOffset)^ do fBuf := MapViewOfFile(fMap,FILE_MAP_READ,Hi,Lo,fBufSize); if fBuf=nil then begin // Windows failed to find a contiguous VA space -> fall back on direct read @@ -59771,10 +59044,10 @@ function TMemoryMap.Map(aFile: THandle; aCustomSize: PtrUInt; aCustomOffset: Int fMap := 0; {$else} if aCustomOffset<>0 then - if aCustomOffset and (_SC_PAGE_SIZE-1)<>0 then - raise ESynException.CreateUTF8('fpmmap(aCustomOffset=%) with pagesize=%', - [aCustomOffset,_SC_PAGE_SIZE]) else - aCustomOffset := aCustomOffset div _SC_PAGE_SIZE; + if aCustomOffset and (SystemInfo.dwPageSize-1)<>0 then + raise ESynException.CreateUTF8('fpmmap(aCustomOffset=%) with SystemInfo.dwPageSize=%', + [aCustomOffset,SystemInfo.dwPageSize]) else + aCustomOffset := aCustomOffset div SystemInfo.dwPageSize; fBuf := {$ifdef KYLIX3}mmap{$else}fpmmap{$endif}( nil,fBufSize,PROT_READ,MAP_SHARED,fFile,aCustomOffset); if fBuf=MAP_FAILED then begin @@ -60001,7 +59274,7 @@ procedure TFileBufferWriter.Write(Data: pointer; DataLen: integer); exit; end; end; - MoveFast(Data^,fBuffer^[fPos],DataLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Data^,fBuffer^[fPos],DataLen); inc(fPos,DataLen); end; @@ -60017,7 +59290,7 @@ procedure TFileBufferWriter.WriteN(Data: Byte; Count: integer); fStream.WriteBuffer(fBuffer^,fPos); fPos := 0; end; - FillcharFast(fBuffer^[fPos],len,Data); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(fBuffer^[fPos],len,Data); inc(fPos,len); dec(Count,len); end; @@ -60058,7 +59331,7 @@ procedure TFileBufferWriter.Write4(Data: integer); procedure TFileBufferWriter.Write4BigEndian(Data: integer); begin - Write4(bswap32(Data)); + Write4({$ifdef FPC}SwapEndian{$else}bswap32{$endif}(Data)); end; procedure TFileBufferWriter.Write8(const Data8Bytes); @@ -60243,7 +59516,7 @@ procedure TFileBufferWriter.WriteRawUTF8DynArray(const Values: TRawUTF8DynArray; break; // avoid buffer overflow end; P := ToVarUInt32(len,P); - MoveFast(pointer(PI^[i])^,P^,len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(PI^[i])^,P^,len); inc(P,len); end else // fixed size strings case @@ -60252,7 +59525,7 @@ procedure TFileBufferWriter.WriteRawUTF8DynArray(const Values: TRawUTF8DynArray; n := i; break; // avoid buffer overflow end; - MoveFast(pointer(PI^[i])^,P^,fixedsize); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(PI^[i])^,P^,fixedsize); inc(P,fixedsize); end; len := PAnsiChar(P)-PBeg; // format: Isize+varUInt32s*strings @@ -60467,7 +59740,7 @@ procedure TFileBufferWriter.WriteVarUInt32Values(Values: PIntegerArray; n := (fBufLen-fPos)shr 2; if ValuesCountDataLen then len := DataLen; if Data<>nil then - MoveFast(fMap.fBuf[fCurrentPos],Data^,len); + {$ifdef FPC}Move{$else}MoveFast{$endif}(fMap.fBuf[fCurrentPos],Data^,len); inc(fCurrentPos,len); result := len; end else @@ -61172,476 +60446,6 @@ function TFileBufferReader.Seek(Offset: PtrInt): boolean; end; -{ TFastReader } - -procedure TFastReader.Init(Buffer: pointer; Len: integer); -begin - P := Buffer; - Last := P+Len; - OnErrorOverflow := nil; - OnErrorData := nil; - Tag := 0; -end; - -procedure TFastReader.Init(const Buffer: RawByteString); -begin - Init(pointer(Buffer),length(Buffer)); -end; - -procedure TFastReader.ErrorOverflow; -begin - if Assigned(OnErrorOverflow) then - OnErrorOverflow else - raise EFastReader.Create('Reached End of Input'); -end; - -procedure TFastReader.ErrorData(const fmt: RawUTF8; const args: array of const); -begin - if Assigned(OnErrorData) then - OnErrorData(fmt,args) else - raise EFastReader.CreateUTF8('Incorrect Data: '+fmt,args); -end; - -function TFastReader.EOF: boolean; -begin - result := P>=Last; -end; - -function TFastReader.RemainingLength: PtrUInt; -begin - result := PtrUInt(Last)-PtrUInt(P); -end; - -function TFastReader.NextByte: byte; -begin - if P>=Last then - ErrorOverflow; - result := ord(P^); - inc(P); -end; - -function TFastReader.Next4: cardinal; -begin - if P+3>=Last then - ErrorOverflow; - result := PCardinal(P)^; - inc(P,4); -end; - -function TFastReader.Next8: QWord; -begin - if P+7>=Last then - ErrorOverflow; - result := PQWord(P)^; - inc(P,8); -end; - -function TFastReader.NextByteEquals(Value: byte): boolean; -begin - if P>=Last then - ErrorOverflow; - if ord(P^) = Value then begin - inc(P); - result := true; - end else - result := false; -end; - -function TFastReader.Next(DataLen: PtrInt): pointer; -begin - if P+DataLen>Last then - ErrorOverflow; - result := P; - inc(P,DataLen); -end; - -function TFastReader.NextSafe(out Data: Pointer; DataLen: PtrInt): boolean; -begin - if P+DataLen>Last then - result := false else begin - Data := P; - inc(P,DataLen); - result := true; - end; -end; - -procedure TFastReader.Copy(out Dest; DataLen: PtrInt); -begin - if P+DataLen>Last then - ErrorOverflow; - MoveFast(P^,Dest,DataLen); - inc(P,DataLen); -end; - -function TFastReader.CopySafe(out Dest; DataLen: PtrInt): boolean; -begin - if P+DataLen>Last then - result := false else begin - MoveFast(P^,Dest,DataLen); - inc(P,DataLen); - result := true; - end; -end; - -procedure TFastReader.VarBlob(out result: TValueResult); -begin - result.Len := VarUInt32; - if P+result.Len>Last then - ErrorOverflow; - result.Ptr := P; - inc(P,result.Len); -end; - -function TFastReader.VarBlob: TValueResult; -begin - result.Len := VarUInt32; - if P+result.Len>Last then - ErrorOverflow; - result.Ptr := P; - inc(P,result.Len); -end; - -{$ifndef NOVARIANTS} -procedure TFastReader.NextVariant(var Value: variant; CustomVariantOptions: pointer); -begin - P := VariantLoad(Value, P, CustomVariantOptions); - if P=nil then - ErrorData('VariantLoad=nil',[]) else - if P>Last then - ErrorOverFlow; -end; - -procedure FastReaderDocVariant(var Value: TDocVariantData; - const JSON: TValueResult; CustomVariantOptions: PDocVariantOptions); -var - temp: TSynTempBuffer; -begin - temp.Init(JSON.Ptr,JSON.Len); // parsing will modify input buffer in-place - try - if CustomVariantOptions=nil then - CustomVariantOptions := @JSON_OPTIONS[true]; - Value.InitJSONInPlace(temp.buf,CustomVariantOptions^); - finally - temp.Done; - end; -end; - -procedure TFastReader.NextDocVariantData(out Value: variant; CustomVariantOptions: pointer); -var json: TValueResult; -begin - VarBlob(json); - if json.Len > 0 then - FastReaderDocVariant(TDocVariantData(Value), json, CustomVariantOptions); -end; -{$endif NOVARIANTS} - -function TFastReader.VarInt32: integer; -begin - result := VarUInt32; - if result and 1<>0 then - // 1->1, 3->2.. - result := result shr 1+1 else - // 0->0, 2->-1, 4->-2.. - result := -(result shr 1); -end; - -function TFastReader.VarInt64: Int64; -begin - result := VarUInt64; - if result and 1<>0 then - // 1->1, 3->2.. - result := result shr 1+1 else - // 0->0, 2->-1, 4->-2.. - result := -(result shr 1); -end; - -function TFastReader.VarUInt32: cardinal; -var c: cardinal; -{$ifdef CPUX86} // not enough CPU registers -label err; -begin - result := ord(P^); - if P>=Last then - goto err; - inc(P); - if result<=$7f then - exit; - if P>=Last then - goto err; - c := ord(P^) shl 7; - inc(P); - result := result and $7F or c; - if c<=$7f shl 7 then - exit; // Values between 128 and 16256 - if P>=Last then - goto err; - c := ord(P^) shl 14; - inc(P); - result := result and $3FFF or c; - if c<=$7f shl 14 then - exit; // Values between 16257 and 2080768 - if P>=Last then - goto err; - c := ord(P^) shl 21; - inc(P); - result := result and $1FFFFF or c; - if c<=$7f shl 21 then - exit; // Values between 2080769 and 266338304 - if P>=Last then -err:ErrorOverflow; - c := ord(P^) shl 28; - inc(P); - result := result and $FFFFFFF or c; -{$else} - s,l: PByte; -label err,fin; -begin - s := pointer(P); - l := pointer(Last); - result := s^; - if PAnsiChar(s)>=PAnsiChar(l) then - goto err; - inc(s); - if result<=$7f then - goto fin; - if PAnsiChar(s)>=PAnsiChar(l) then - goto err; - c := s^ shl 7; - inc(s); - result := result and $7F or c; - if c<=$7f shl 7 then - goto fin; // Values between 128 and 16256 - if PAnsiChar(s)>=PAnsiChar(l) then - goto err; - c := s^ shl 14; - inc(s); - result := result and $3FFF or c; - if c<=$7f shl 14 then - goto fin; // Values between 16257 and 2080768 - if PAnsiChar(s)>=PAnsiChar(l) then - goto err; - c := s^ shl 21; - inc(s); - result := result and $1FFFFF or c; - if c<=$7f shl 21 then - goto fin; // Values between 2080769 and 266338304 - if PAnsiChar(s)>=PAnsiChar(l) then -err:ErrorOverflow; - c := s^ shl 28; - inc(s); - result := result and $FFFFFFF or c; -fin: - P := pointer(s); -{$endif} -end; - -procedure TFastReader.VarNextInt; -{$ifdef CPUX86} // not enough CPU registers -begin - repeat - if P>=Last then - break; // reached end of input - if P^<=#$7f then - break; // reached end of VarUInt32/VarUInt64 - inc(P); - until false; - inc(P); -{$else} -var s: PAnsiChar; -begin - s := P; - repeat - if s>=Last then - break; // reached end of input - if s^<=#$7f then - break; // reached end of VarUInt32/VarUInt64 - inc(s); - until false; - P := s+1; -{$endif CPUX86} -end; - -procedure TFastReader.VarNextInt(count: integer); -{$ifdef CPUX86} // not enough CPU registers -begin - if count=0 then - exit; - repeat - if P>=Last then - break; // reached end of input - if P^>#$7f then begin - inc(P); - continue; // didn't reach end of VarUInt32/VarUInt64 - end; - inc(P); - dec(count); - if count=0 then - break; - until false; -{$else} -var s, max: PAnsiChar; -begin - if count=0 then - exit; - s := P; - max := Last; - repeat - if s>=max then - break; // reached end of input - if s^>#$7f then begin - inc(s); - continue; // didn't reach end of VarUInt32/VarUInt64 - end; - inc(s); - dec(count); - if count=0 then - break; - until false; - P := s; -{$endif CPUX86} -end; - -function TFastReader.PeekVarInt32(out value: PtrInt): boolean; -begin - result := PeekVarUInt32(PtrUInt(value)); - if result then - if value and 1<>0 then - // 1->1, 3->2.. - value := value shr 1+1 else - // 0->0, 2->-1, 4->-2.. - value := -(value shr 1); -end; - -function TFastReader.PeekVarUInt32(out value: PtrUInt): boolean; -var s: PAnsiChar; -begin - result := false; - s := P; - repeat - if s>=Last then - exit; // reached end of input -> returns false - if s^<=#$7f then - break; // reached end of VarUInt32 - inc(s); - until false; - s := P; - value := VarUInt32; // fast value decode - P := s; // rewind - result := true; -end; - -function TFastReader.VarUInt32Safe(out Value: cardinal): boolean; -var c, n, v: cardinal; -begin - result := false; - if P>=Last then - exit; - v := ord(P^); - inc(P); - if v>$7f then begin - n := 0; - v := v and $7F; - repeat - if P>=Last then - exit; - c := ord(P^); - inc(P); - inc(n,7); - if c<=$7f then break; - v := v or ((c and $7f) shl n); - until false; - v := v or (c shl n); - end; - Value := v; - result := true; // success -end; - -function TFastReader.VarUInt64: QWord; -label err; -var c, n: PtrUInt; -begin - if P>=Last then -err: ErrorOverflow; - c := ord(P^); - inc(P); - if c>$7f then begin - result := c and $7F; - n := 0; - repeat - if P>=Last then - goto err; - c := ord(P^); - inc(P); - inc(n,7); - if c<=$7f then break; - result := result or (QWord(c and $7f) shl n); - until false; - result := result or (QWord(c) shl n); - end else - result := c; -end; - -function TFastReader.VarString: RawByteString; -begin - with VarBlob do - SetString(result,Ptr,Len); -end; - -procedure TFastReader.VarUTF8(out result: RawUTF8); -begin - with VarBlob do - FastSetString(result,Ptr,Len); -end; - -function TFastReader.VarUTF8: RawUTF8; -begin - with VarBlob do - FastSetString(result,Ptr,Len); -end; - -function TFastReader.VarShortString: shortstring; -begin - with VarBlob do - SetString(result,Ptr,Len); -end; - -function TFastReader.VarUTF8Safe(out Value: RawUTF8): boolean; -var len: cardinal; -begin - if VarUInt32Safe(len) and (P+len<=Last) then begin - FastSetString(Value,P,len); - inc(P,len); - result := true; - end else - result := false; -end; - -procedure TFastReader.Read(var DA: TDynArray; NoCheckHash: boolean); -begin - P := DA.LoadFrom(P,nil,NoCheckHash); - if P=nil then - ErrorData('TDynArray.LoadFrom %',[DA.ArrayTypeName]); -end; - -function TFastReader.ReadVarUInt32Array(var Values: TIntegerDynArray): PtrInt; -var i: integer; - k: TFileBufferWriterKind; -begin - result := VarUInt32; - SetLength(Values,result); - Copy(k,1); - if k=wkUInt32 then begin - Copy(Values[0],result*4); - exit; - end; - Next(4); // format: Isize+varUInt32s - case k of - wkVarInt32: for i := 0 to result-1 do Values[i] := VarInt32; - wkVarUInt32: for i := 0 to result-1 do Values[i] := VarUInt32; - else ErrorData('ReadVarUInt32Array: unhandled kind=%', [ord(k)]); - end; -end; - function PropNameValid(P: PUTF8Char): boolean; begin @@ -61731,10 +60535,10 @@ function StrCompL(P1,P2: PUTF8Char; L, Default: Integer): PtrInt; function StrCompIL(P1,P2: PUTF8Char; L, Default: Integer): PtrInt; var i: PtrInt; - tab: {$ifdef CPUX86}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; + tab: {$ifdef CPUX86NOTPIC}TNormTable absolute NormToUpperAnsi7{$else}PNormTable{$endif}; begin i := 0; - {$ifndef CPUX86}tab := @NormToUpperAnsi7;{$endif} // faster on PIC and x86_64 + {$ifndef CPUX86NOTPIC}tab := @NormToUpperAnsi7;{$endif} // faster on PIC and x86_64 repeat if tab[P1[i]]=tab[P2[i]] then begin inc(i); @@ -62114,7 +60918,7 @@ function StreamUnSynLZ(Source: TStream; Magic: cardinal): TMemoryStream; if not stored then if SynLZdecompressdestlen(S)<>Head.UnCompressedSize then exit; - if Hash32(S,Head.CompressedSize)<>Head.HashCompressed then + if Hash32(pointer(S),Head.CompressedSize)<>Head.HashCompressed then exit; if result=nil then result := THeapMemoryStream.Create else begin @@ -62129,10 +60933,10 @@ function StreamUnSynLZ(Source: TStream; Magic: cardinal): TMemoryStream; D := PAnsiChar(result.Memory)+resultSize; inc(resultSize,Head.UnCompressedSize); if stored then - MoveFast(S^,D^,Head.CompressedSize) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(S^,D^,Head.CompressedSize) else if SynLZDecompress1(S,Head.CompressedSize,D)<>Head.UnCompressedSize then FreeAndNil(result) else - if Hash32(D,Head.UnCompressedSize)<>Head.HashUncompressed then + if Hash32(pointer(D),Head.UnCompressedSize)<>Head.HashUncompressed then FreeAndNil(result); until (result=nil) or (sourcePosition>=sourceSize); end; @@ -62174,12 +60978,23 @@ class function TAlgoCompress.Algo(const Comp: TByteDynArray): TAlgoCompress; class function TAlgoCompress.Algo(Comp: PAnsiChar; CompLen: integer): TAlgoCompress; begin if (Comp<>nil) and (CompLen>9) then - if ord(Comp[4])<=1{inlinedCOMPRESS_SYNLZ} then // "stored" maps SynLZ - result := AlgoSynLZ else + if ord(Comp[4])<=1 then // inline-friendly Comp[4]<=COMPRESS_SYNLZ + result := AlgoSynLZ else // COMPRESS_STORED is also handled as SynLZ result := Algo(ord(Comp[4])) else result := nil; end; +class function TAlgoCompress.Algo(Comp: PAnsiChar; CompLen: integer; out IsStored: boolean): TAlgoCompress; +begin + if (Comp<>nil) and (CompLen>9) then begin + IsStored := Comp[4]=COMPRESS_STORED; + result := Algo(ord(Comp[4])); + end else begin + IsStored := false; + result := nil; + end; +end; + class function TAlgoCompress.Algo(AlgoID: byte): TAlgoCompress; var i: integer; ptr: ^TAlgoCompress; @@ -62200,12 +61015,17 @@ class function TAlgoCompress.Algo(AlgoID: byte): TAlgoCompress; end; end; +class function TAlgoCompress.UncompressedSize(const Comp: RawByteString): integer; +begin + result := Algo(Comp).DecompressHeader(pointer(Comp),length(Comp)); +end; + function TAlgoCompress.AlgoName: TShort16; var s: PShortString; i: integer; begin if self=nil then - result := 'stored' else begin + result := 'none' else begin s := ClassNameShort(self); if IdemPChar(@s^[1],'TALGO') then begin result[0] := AnsiChar(ord(s^[0])-5); @@ -62224,57 +61044,61 @@ function TAlgoCompress.AlgoHash(Previous: cardinal; Data: pointer; DataLen: inte result := crc32c(Previous,Data,DataLen); end; -function TAlgoCompress.Compress(const Plain: RawByteString; - CompressionSizeTrigger: integer; CheckMagicForCompressed: boolean): RawByteString; +function TAlgoCompress.Compress(const Plain: RawByteString; CompressionSizeTrigger: integer; + CheckMagicForCompressed: boolean; BufferOffset: integer): RawByteString; begin - result := Compress(pointer(Plain),Length(Plain),CompressionSizeTrigger,CheckMagicForCompressed); + result := Compress(pointer(Plain),Length(Plain),CompressionSizeTrigger, + CheckMagicForCompressed,BufferOffset); end; -function TAlgoCompress.Compress(Plain: PAnsiChar; PlainLen: integer; - CompressionSizeTrigger: integer; CheckMagicForCompressed: boolean): RawByteString; +function TAlgoCompress.Compress(Plain: PAnsiChar; PlainLen: integer; CompressionSizeTrigger: integer; + CheckMagicForCompressed: boolean; BufferOffset: integer): RawByteString; var len: integer; R: PAnsiChar; crc: cardinal; - tmp: array[0..16383] of AnsiChar; // will resize Result in-place + tmp: array[0..16383] of AnsiChar; // big enough to resize Result in-place begin - if (self=nil) or (PlainLen=0) or (Plain=nil) then + if (self=nil) or (PlainLen=0) or (Plain=nil) then begin + result := ''; exit; + end; crc := AlgoHash(0,Plain,PlainLen); if (PlainLenSizeOf(tmp) then begin SetString(result,nil,len); R := pointer(result); end else R := @tmp; + inc(R,BufferOffset); PCardinal(R)^ := crc; len := AlgoCompress(Plain,PlainLen,R+9); - if len>=PlainLen then begin // store if compression not worth it + if len+64>=PlainLen then begin // store if compression was not worth it R[4] := COMPRESS_STORED; PCardinal(R+5)^ := crc; - MoveFast(Plain^,R[9],PlainLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Plain^,R[9],PlainLen); len := PlainLen; end else begin R[4] := AnsiChar(AlgoID); PCardinal(R+5)^ := AlgoHash(0,R+9,len); end; - if R=@tmp then - SetString(result,tmp,len+9) else - SetLength(result,len+9); // resize in-place may not move any data + if R=@tmp[BufferOffset] then + SetString(result,tmp,len+BufferOffset+9) else + SetLength(result,len+BufferOffset+9); // MM may not move the data end; end; -function TAlgoCompress.Compress(Plain, Comp: PAnsiChar; PlainLen, - CompLen: integer; CompressionSizeTrigger: integer; - CheckMagicForCompressed: boolean): integer; +function TAlgoCompress.Compress(Plain, Comp: PAnsiChar; PlainLen, CompLen: integer; + CompressionSizeTrigger: integer; CheckMagicForCompressed: boolean): integer; var len: integer; begin result := 0; @@ -62296,7 +61120,7 @@ function TAlgoCompress.Compress(Plain, Comp: PAnsiChar; PlainLen, end; Comp[4] := COMPRESS_STORED; PCardinal(Comp+5)^ := PCardinal(Comp)^; - MoveFast(Plain^,Comp[9],PlainLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Plain^,Comp[9],PlainLen); result := PlainLen+9; end; @@ -62322,7 +61146,7 @@ function TAlgoCompress.CompressToBytes(Plain: PAnsiChar; PlainLen: integer; PCardinal(R)^ := crc; R[4] := COMPRESS_STORED; PCardinal(R+5)^ := crc; - MoveFast(Plain^,R[9],PlainLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Plain^,R[9],PlainLen); end else begin SetLength(result,CompressDestLen(PlainLen)); R := pointer(result); @@ -62331,7 +61155,7 @@ function TAlgoCompress.CompressToBytes(Plain: PAnsiChar; PlainLen: integer; if len>=PlainLen then begin // store if compression not worth it R[4] := COMPRESS_STORED; PCardinal(R+5)^ := crc; - MoveFast(Plain^,R[9],PlainLen); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Plain^,R[9],PlainLen); len := PlainLen; end else begin R[4] := AnsiChar(AlgoID); @@ -62354,21 +61178,23 @@ function TAlgoCompress.Decompress(const Comp: TByteDynArray): RawByteString; end; procedure TAlgoCompress.Decompress(Comp: PAnsiChar; CompLen: integer; - out Result: RawByteString; Load: TAlgoCompressLoad); + out Result: RawByteString; Load: TAlgoCompressLoad; BufferOffset: integer); var len: integer; + dec: PAnsiChar; begin len := DecompressHeader(Comp,CompLen,Load); if len=0 then exit; - SetString(result,nil,len); - if not DecompressBody(Comp,pointer(result),CompLen,len,Load) then + SetString(result,nil,len+BufferOffset); + dec := pointer(result); + if not DecompressBody(Comp,dec+BufferOffset,CompLen,len,Load) then result := ''; end; function TAlgoCompress.Decompress(const Comp: RawByteString; - Load: TAlgoCompressLoad): RawByteString; + Load: TAlgoCompressLoad; BufferOffset: integer): RawByteString; begin - Decompress(pointer(Comp),length(Comp),result,Load); + Decompress(pointer(Comp),length(Comp),result,Load,BufferOffset); end; function TAlgoCompress.TryDecompress(const Comp: RawByteString; @@ -62426,7 +61252,7 @@ function TAlgoCompress.DecompressPartial(Comp, Partial: PAnsiChar; if PartialLen>BodyLen then PartialLen := BodyLen; if Comp[4]=COMPRESS_STORED then - MoveFast(Comp[9],Partial[0],PartialLen) else + {$ifdef FPC}Move{$else}MoveFast{$endif}(Comp[9],Partial[0],PartialLen) else if AlgoDecompressPartial(Comp+9,CompLen-9,Partial,PartialLen,PartialLenMax)=pEnd then break; i := ord(p^); - if not (AnsiChar(i) in ANSICHARNOT01310) then break; + if i in [10,13] then break; if NormToUpperAnsi7Byte[i]=ord(up^) then goto Fnd; inc(p); if p>=pEnd then break; i := ord(p^); - if not (AnsiChar(i) in ANSICHARNOT01310) then break; + if i in [10,13] then break; if NormToUpperAnsi7Byte[i]=ord(up^) then goto Fnd; inc(p); if p>=pEnd then break; i := ord(p^); - if not (AnsiChar(i) in ANSICHARNOT01310) then break; + if i in [10,13] then break; if NormToUpperAnsi7Byte[i]=ord(up^) then goto Fnd; inc(p); if p>=pEnd then break; i := ord(p^); - if not (AnsiChar(i) in ANSICHARNOT01310) then break; + if i in [10,13] then break; if NormToUpperAnsi7Byte[i]<>ord(up^) then begin inc(p); continue; @@ -62803,7 +61629,7 @@ function TMemoryMapText.LineSize(aIndex: integer): integer; function GetLineSizeSmallerThan(P,PEnd: PUTF8Char; aMinimalCount: integer): boolean; begin if P<>nil then - while (PCount then Result := Count; - MoveFast(PByteArray(fDataString)[fPosition],Buffer,Result); + {$ifdef FPC}Move{$else}MoveFast{$endif}(PByteArray(fDataString)[fPosition],Buffer,Result); inc(fPosition, Result); end; end; @@ -62924,7 +61750,7 @@ function TRawByteStringStream.Write(const Buffer; Count: Integer): Longint; Result := 0 else begin Result := Count; SetLength(fDataString,fPosition+Result); - MoveFast(Buffer,PByteArray(fDataString)[fPosition],Result); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Buffer,PByteArray(fDataString)[fPosition],Result); inc(FPosition,Result); end; end; @@ -62948,109 +61774,6 @@ function TFakeWriterStream.Seek(Offset: Longint; Origin: Word): Longint; end; - -{ TPendingTaskList } - -constructor TPendingTaskList.Create; -begin - fSafe.Init; - fTasks.InitSpecific(TypeInfo(TPendingTaskListItemDynArray),fTask,djInt64,@fCount); -end; - -destructor TPendingTaskList.Destroy; -begin - fSafe.Done; - inherited Destroy; -end; - -function TPendingTaskList.GetTimestamp: Int64; -begin - result := GetTickCount64; -end; - -procedure TPendingTaskList.AddTask(aMilliSecondsDelayFromNow: integer; - const aTask: RawByteString); -var item: TPendingTaskListItem; - ndx: integer; -begin - item.Timestamp := GetTimestamp+aMilliSecondsDelayFromNow; - item.Task := aTask; - fSafe.Lock; - try - if fTasks.FastLocateSorted(item,ndx) then - inc(ndx); // always insert just after any existing timestamp - fTasks.FastAddSorted(ndx,item); - finally - fSafe.UnLock; - end; -end; - -procedure TPendingTaskList.AddTasks( - const aMilliSecondsDelays: array of integer; - const aTasks: array of RawByteString); -var item: TPendingTaskListItem; - i,ndx: integer; -begin - if length(aTasks)<>length(aMilliSecondsDelays) then - exit; - item.Timestamp := GetTimestamp; - fSafe.Lock; - try - for i := 0 to High(aTasks) do begin - inc(item.Timestamp,aMilliSecondsDelays[i]); - item.Task := aTasks[i]; - if fTasks.FastLocateSorted(item,ndx) then - inc(ndx); // always insert just after any existing timestamp - fTasks.FastAddSorted(ndx,item); - end; - finally - fSafe.UnLock; - end; -end; - -function TPendingTaskList.GetCount: integer; -begin - if self=nil then - result := 0 else begin - fSafe.Lock; - try - result := fCount; - finally - fSafe.UnLock; - end; - end; -end; - -function TPendingTaskList.NextPendingTask: RawByteString; -begin - result := ''; - if (self=nil) or (fCount=0) then - exit; - fSafe.Lock; - try - if fCount>0 then - if GetTimestamp>=fTask[0].Timestamp then begin - result := fTask[0].Task; - fTasks.FastDeleteSorted(0); - end; - finally - fSafe.UnLock; - end; -end; - -procedure TPendingTaskList.Clear; -begin - if (self=nil) or (fCount=0) then - exit; - fSafe.Lock; - try - fTasks.Clear; - finally - fSafe.UnLock; - end; -end; - - { TSynNameValue } procedure TSynNameValue.Add(const aName, aValue: RawUTF8; aTag: PtrInt); @@ -63077,7 +61800,7 @@ procedure TSynNameValue.InitFromIniSection(Section: PUTF8Char; fOnAdd := OnAdd; while (Section<>nil) and (Section^<>'[') do begin s := GetNextLine(Section,Section); - i := PosEx('=',s); + i := PosExChar('=',s); if (i>1) and not(s[1] in [';','[']) then if Assigned(OnTheFlyConvert) then Add(copy(s,1,i-1),OnTheFlyConvert(copy(s,i+1,1000))) else @@ -63149,7 +61872,7 @@ procedure TSynNameValue.Init(aCaseSensitive: boolean); List := nil; fDynArray.HashInvalidate; // initialize hashed storage - FillcharFast(self,SizeOf(self),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(self,SizeOf(self),0); fDynArray.InitSpecific(TypeInfo(TSynNameValueItemDynArray),List, djRawUTF8,@Count,not aCaseSensitive); end; @@ -63411,8 +62134,6 @@ function TSynNameValue.MergeDocVariant(var DocVariant: variant; {$endif NOVARIANTS} -{ TSynBackgroundThreadAbstract } - {$ifdef MSWINDOWS} function IsDebuggerPresent: BOOL; stdcall; external kernel32; // since XP {$endif} @@ -63474,1357 +62195,6 @@ procedure SetThreadNameDefault(ThreadID: TThreadID; const Name: RawUTF8); {$endif FPC} end; -{$ifdef KYLIX3} -type - // see http://stackoverflow.com/a/3085509/458259 about this known Kylix bug - TEventHack = class(THandleObject) // should match EXACTLY SyncObjs.pas source! - private - FEvent: TSemaphore; - FManualReset: Boolean; - end; - -function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult; -var E: TEventHack absolute Event; - procedure SetResult(res: integer); - begin - if res=0 then - result := wrSignaled else - if errno in [EAGAIN,ETIMEDOUT] then - result := wrTimeOut else begin - write(TimeOut,':',errno,' '); - result := wrError; - end; - end; -{.$define USESEMTRYWAIT} -// sem_timedwait() is slower than sem_trywait(), but consuming much less CPU -{$ifdef USESEMTRYWAIT} -var time: timespec; -{$else} -var start,current: Int64; - elapsed: LongWord; -{$endif} -begin - if Timeout=INFINITE then begin - SetResult(sem_wait(E.FEvent)); - exit; - end; - if TimeOut=0 then begin - SetResult(sem_trywait(E.FEvent)); - exit; - end; - {$ifdef USESEMTRYWAIT} - clock_gettime(CLOCK_REALTIME,time); - inc(time.tv_sec,TimeOut div 1000); - inc(time.tv_nsec,(TimeOut mod 1000)*1000000); - while time.tv_nsec>1000000000 do begin - inc(time.tv_sec); - dec(time.tv_nsec,1000000000); - end; - SetResult(sem_timedwait(E.FEvent,time)); - {$else} - start := GetTickCount64; - repeat - if sem_trywait(E.FEvent)=0 then begin - result := wrSignaled; - break; - end; - current := GetTickCount64; - elapsed := current-start; - if elapsed=0 then - sched_yield else - if elapsed>TimeOut then begin - result := wrTimeOut; - break; - end else - if elapsed<5 then - usleep(50) else - usleep(1000); - until false; - {$endif} - if E.FManualReset then begin - repeat until sem_trywait(E.FEvent)<>0; // reset semaphore state - sem_post(E.FEvent); - end; -end; - -{$else KYLIX3} // original FPC or Windows implementation is OK - -function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult; -begin - result := Event.WaitFor(TimeOut); -end; - -{$endif KYLIX3} - -procedure FixedWaitForever(Event: TEvent); -begin - FixedWaitFor(Event,INFINITE); -end; - -constructor TSynBackgroundThreadAbstract.Create(const aThreadName: RawUTF8; - OnBeforeExecute,OnAfterExecute: TNotifyThreadEvent; CreateSuspended: boolean); -begin - fProcessEvent := TEvent.Create(nil,false,false,''); - fThreadName := aThreadName; - fOnBeforeExecute := OnBeforeExecute; - fOnAfterExecute := OnAfterExecute; - inherited Create(CreateSuspended{$ifdef FPC},512*1024{$endif}); // DefaultStackSize=512KB -end; - -{$ifndef HASTTHREADSTART} -procedure TSynBackgroundThreadAbstract.Start; -begin - Resume; -end; -{$endif} - -{$ifndef HASTTHREADTERMINATESET} -procedure TSynBackgroundThreadAbstract.Terminate; -begin - inherited Terminate; // FTerminated := True - TerminatedSet; -end; -{$endif} - -procedure TSynBackgroundThreadAbstract.TerminatedSet; -begin - fProcessEvent.SetEvent; // ExecuteLoop should handle Terminated flag -end; - -procedure TSynBackgroundThreadAbstract.WaitForNotExecuting(maxMS: integer); -var endtix: Int64; -begin - if fExecute = exRun then begin - endtix := GetTickCount64+maxMS; - repeat - Sleep(1); // wait for Execute to finish - until (fExecute <> exRun) or (GetTickCount64>=endtix); - end; -end; - -destructor TSynBackgroundThreadAbstract.Destroy; -begin - if fExecute = exRun then begin - Terminate; - WaitForNotExecuting(100); - end; - inherited Destroy; - FreeAndNil(fProcessEvent); -end; - -procedure TSynBackgroundThreadAbstract.SetExecuteLoopPause(dopause: boolean); -begin - if Terminated or (dopause=fExecuteLoopPause) or (fExecute=exFinished) then - exit; - fExecuteLoopPause := dopause; - fProcessEvent.SetEvent; // notify Execute main loop -end; - -procedure TSynBackgroundThreadAbstract.Execute; -begin - try - if fThreadName='' then - SetCurrentThreadName('%(%)',[self,pointer(self)]) else - SetCurrentThreadName('%',[fThreadName]); - if Assigned(fOnBeforeExecute) then - fOnBeforeExecute(self); - try - fExecute := exRun; - while not Terminated do - if fExecuteLoopPause then - FixedWaitFor(fProcessEvent,100) else - ExecuteLoop; - finally - if Assigned(fOnAfterExecute) then - fOnAfterExecute(self); - end; - finally - fExecute := exFinished; - end; -end; - -{ TSynBackgroundThreadMethodAbstract } - -constructor TSynBackgroundThreadMethodAbstract.Create(aOnIdle: TOnIdleSynBackgroundThread; - const aThreadName: RawUTF8; OnBeforeExecute,OnAfterExecute: TNotifyThreadEvent); -begin - fOnIdle := aOnIdle; // cross-platform may run Execute as soon as Create is called - fCallerEvent := TEvent.Create(nil,false,false,''); - fPendingProcessLock.Init; - inherited Create(aThreadName,OnBeforeExecute,OnAfterExecute); -end; - -destructor TSynBackgroundThreadMethodAbstract.Destroy; -begin - SetPendingProcess(flagDestroying); - fProcessEvent.SetEvent; // notify terminated - FixedWaitForever(fCallerEvent); // wait for actual termination - FreeAndNil(fCallerEvent); - inherited Destroy; - fPendingProcessLock.Done; -end; - -function TSynBackgroundThreadMethodAbstract.GetPendingProcess: TSynBackgroundThreadProcessStep; -begin - fPendingProcessLock.Lock; - result := fPendingProcessFlag; - fPendingProcessLock.UnLock; -end; - -procedure TSynBackgroundThreadMethodAbstract.SetPendingProcess(State: TSynBackgroundThreadProcessStep); -begin - fPendingProcessLock.Lock; - fPendingProcessFlag := State; - fPendingProcessLock.UnLock; -end; - -procedure TSynBackgroundThreadMethodAbstract.ExecuteLoop; -{$ifndef DELPHI5OROLDER} -var E: TObject; -{$endif} -begin - case FixedWaitFor(fProcessEvent,INFINITE) of - wrSignaled: - case GetPendingProcess of - flagDestroying: begin - fCallerEvent.SetEvent; // abort caller thread process - Terminate; // forces Execute loop ending - exit; - end; - flagStarted: - if not Terminated then - if fExecuteLoopPause then // pause -> try again later - fProcessEvent.SetEvent else - try - fBackgroundException := nil; - try - if Assigned(fOnBeforeProcess) then - fOnBeforeProcess(self); - try - Process; - finally - if Assigned(fOnAfterProcess) then - fOnAfterProcess(self); - end; - except - {$ifdef DELPHI5OROLDER} - on E: Exception do - fBackgroundException := ESynException.CreateUTF8( - 'Redirected %: "%"',[E,E.Message]); - {$else} - E := AcquireExceptionObject; - if E.InheritsFrom(Exception) then - fBackgroundException := Exception(E); - {$endif} - end; - finally - SetPendingProcess(flagFinished); - fCallerEvent.SetEvent; - end; - end; - end; -end; - -function TSynBackgroundThreadMethodAbstract.AcquireThread: TSynBackgroundThreadProcessStep; -begin - fPendingProcessLock.Lock; - try - result := fPendingProcessFlag; - if result=flagIdle then begin // we just acquired the thread! congrats! - fPendingProcessFlag := flagStarted; // atomic set "started" flag - fCallerThreadID := ThreadID; - end; - finally - fPendingProcessLock.UnLock; - end; -end; - -function TSynBackgroundThreadMethodAbstract.OnIdleProcessNotify(start: Int64): integer; -begin - result := GetTickCount64-start; - if result<0 then - result := MaxInt; // should happen only under XP -> ignore - if Assigned(fOnIdle) then - fOnIdle(self,result) ; -end; - -procedure TSynBackgroundThreadMethodAbstract.WaitForFinished(start: Int64); -var E: Exception; -begin - if (self=nil) or not (fPendingProcessFlag in [flagStarted, flagFinished]) then - exit; // nothing to wait for - try - {$ifdef MSWINDOWS} // do process the OnIdle only if UI - if Assigned(fOnIdle) then begin - while FixedWaitFor(fCallerEvent,100)=wrTimeout do - OnIdleProcessNotify(start); - end else - {$endif} - FixedWaitForever(fCallerEvent); - if fPendingProcessFlag<>flagFinished then - ESynException.CreateUTF8('%.WaitForFinished: flagFinished?',[self]); - if fBackgroundException<>nil then begin - E := fBackgroundException; - fBackgroundException := nil; - raise E; // raise background exception in the calling scope - end; - finally - fParam := nil; - fCallerThreadID := 0; - FreeAndNil(fBackgroundException); - SetPendingProcess(flagIdle); - if Assigned(fOnIdle) then - fOnIdle(self,-1); // notify finished - end; -end; - -function TSynBackgroundThreadMethodAbstract.RunAndWait(OpaqueParam: pointer): boolean; -var start: Int64; - ThreadID: TThreadID; -begin - result := false; - ThreadID := GetCurrentThreadId; - if (self=nil) or (ThreadID=fCallerThreadID) then - // avoid endless loop when waiting in same thread (e.g. UI + OnIdle) - exit; - // 1. wait for any previous request to be finished (should not happen often) - if Assigned(fOnIdle) then - fOnIdle(self,0); // notify started - start := GetTickCount64; - repeat - case AcquireThread of - flagDestroying: - exit; - flagIdle: - break; // we acquired the background thread - end; - case OnIdleProcessNotify(start) of // Windows.GetTickCount64 res is 10-16 ms - 0..20: SleepHiRes(0); - 21..100: SleepHiRes(1); - 101..900: SleepHiRes(5); - else SleepHiRes(50); - end; - until false; - // 2. process execution in the background thread - fParam := OpaqueParam; - fProcessEvent.SetEvent; // notify background thread for Call pending process - WaitForFinished(start); // wait for flagFinished, then set flagIdle - result := true; -end; - -function TSynBackgroundThreadMethodAbstract.GetOnIdleBackgroundThreadActive: boolean; -begin - result := (self<>nil) and Assigned(fOnIdle) and (GetPendingProcess<>flagIdle); -end; - - -{ TSynBackgroundThreadEvent } - -constructor TSynBackgroundThreadEvent.Create(aOnProcess: TOnProcessSynBackgroundThread; - aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); -begin - inherited Create(aOnIdle,aThreadName); - fOnProcess := aOnProcess; -end; - -procedure TSynBackgroundThreadEvent.Process; -begin - if not Assigned(fOnProcess) then - raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]); - fOnProcess(self,fParam); -end; - - -{ TSynBackgroundThreadMethod } - -procedure TSynBackgroundThreadMethod.Process; -var Method: ^TThreadMethod; -begin - if fParam=nil then - raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]); - Method := fParam; - Method^(); -end; - -procedure TSynBackgroundThreadMethod.RunAndWait(Method: TThreadMethod); -var Met: TMethod absolute Method; -begin - inherited RunAndWait(@Met); -end; - - -{ TSynBackgroundThreadProcedure } - -constructor TSynBackgroundThreadProcedure.Create(aOnProcess: TOnProcessSynBackgroundThreadProc; - aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); -begin - inherited Create(aOnIdle,aThreadName); - fOnProcess := aOnProcess; -end; - -procedure TSynBackgroundThreadProcedure.Process; -begin - if not Assigned(fOnProcess) then - raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]); - fOnProcess(fParam); -end; - - -{ TSynParallelProcessThread } - -procedure TSynParallelProcessThread.Process; -begin - if not Assigned(fMethod) then - exit; - fMethod(fIndexStart,fIndexStop); - fMethod := nil; -end; - -procedure TSynParallelProcessThread.Start( - Method: TSynParallelProcessMethod; IndexStart, IndexStop: integer); -begin - fMethod := Method; - fIndexStart := IndexStart; - fIndexStop := IndexStop; - fProcessEvent.SetEvent; // notify execution -end; - - -{ TSynBackgroundThreadProcess } - -constructor TSynBackgroundThreadProcess.Create(const aThreadName: RawUTF8; - aOnProcess: TOnSynBackgroundThreadProcess; aOnProcessMS: cardinal; - aOnBeforeExecute, aOnAfterExecute: TNotifyThreadEvent; - aStats: TSynMonitorClass; CreateSuspended: boolean); -begin - if not Assigned(aOnProcess) then - raise ESynException.CreateUTF8('%.Create(aOnProcess=nil)',[self]); - if aStats<>nil then - fStats := aStats.Create(aThreadName); - fOnProcess := aOnProcess; - fOnProcessMS := aOnProcessMS; - if fOnProcessMS=0 then - fOnProcessMS := INFINITE; // wait until ProcessEvent.SetEvent or Terminated - inherited Create(aThreadName,aOnBeforeExecute,aOnAfterExecute,CreateSuspended); -end; - -destructor TSynBackgroundThreadProcess.Destroy; -begin - if fExecute=exRun then begin - Terminate; - WaitForNotExecuting(10000); // expect the background task to be finished - end; - inherited Destroy; - fStats.Free; -end; - -procedure TSynBackgroundThreadProcess.ExecuteLoop; -var wait: TWaitResult; -begin - wait := FixedWaitFor(fProcessEvent,fOnProcessMS); - if not Terminated and (wait in [wrSignaled,wrTimeout]) then - if fExecuteLoopPause then // pause -> try again later - fProcessEvent.SetEvent else - try - if fStats<>nil then - fStats.ProcessStartTask; - try - fOnProcess(self,wait); - finally - if fStats<>nil then - fStats.ProcessEnd; - end; - except - on E: Exception do begin - if fStats<>nil then - fStats.ProcessErrorRaised(E); - if Assigned(fOnException) then - fOnException(E); - end; - end; -end; - - -{ TSynBackgroundTimer } - -var - ProcessSystemUse: TSystemUse; - -constructor TSynBackgroundTimer.Create(const aThreadName: RawUTF8; - aOnBeforeExecute, aOnAfterExecute: TNotifyThreadEvent; aStats: TSynMonitorClass); -begin - fTasks.Init(TypeInfo(TSynBackgroundTimerTaskDynArray),fTask); - fTaskLock.Init; - {$ifndef NOVARIANTS} - fTaskLock.LockedBool[0] := false; - {$endif} - inherited Create(aThreadName,EverySecond,1000,aOnBeforeExecute,aOnAfterExecute,aStats); -end; - -destructor TSynBackgroundTimer.Destroy; -begin - if (ProcessSystemUse<>nil) and (ProcessSystemUse.fTimer=self) then - ProcessSystemUse.fTimer := nil; // allows processing by another background timer - inherited Destroy; - fTaskLock.Done; -end; - -const - TIXPRECISION = 32; // GetTickCount64 resolution (for aOnProcessSecs=1) - -procedure TSynBackgroundTimer.EverySecond( - Sender: TSynBackgroundThreadProcess; Event: TWaitResult); -var tix: Int64; - i,f,n: integer; - t: ^TSynBackgroundTimerTask; - todo: TSynBackgroundTimerTaskDynArray; // avoid lock contention -begin - if (fTask=nil) or Terminated then - exit; - tix := GetTickCount64; - n := 0; - fTaskLock.Lock; - try - variant(fTaskLock.Padding[0]) := true; - try - for i := 0 to length(fTask)-1 do begin - t := @fTask[i]; - if tix>=t^.NextTix then begin - SetLength(todo,n+1); - todo[n] := t^; - inc(n); - t^.FIFO := nil; // now owned by todo[n].FIFO - t^.NextTix := tix+((t^.Secs*1000)-TIXPRECISION); - end; - end; - finally - fTaskLock.UnLock; - end; - for i := 0 to n-1 do - with todo[i] do - if FIFO<>nil then - for f := 0 to length(FIFO)-1 do - try - OnProcess(self,Event,FIFO[f]); - except - end - else - try - OnProcess(self,Event,''); - except - end; - finally - {$ifdef NOVARIANTS} - fTaskLock.Lock; - variant(fTaskLock.Padding[0]) := false; - fTaskLock.UnLock; - {$else} - fTaskLock.LockedBool[0] := false; - {$endif} - end; -end; - -function TSynBackgroundTimer.Find(const aProcess: TMethod): integer; -begin // caller should have made fTaskLock.Lock; - for result := length(fTask)-1 downto 0 do - with TMethod(fTask[result].OnProcess) do - if (Code=aProcess.Code) and (Data=aProcess.Data) then - exit; - result := -1; -end; - -procedure TSynBackgroundTimer.Enable( - aOnProcess: TOnSynBackgroundTimerProcess; aOnProcessSecs: cardinal); -var task: TSynBackgroundTimerTask; - found: integer; -begin - if (self=nil) or Terminated or not Assigned(aOnProcess) then - exit; - if aOnProcessSecs=0 then begin - Disable(aOnProcess); - exit; - end; - task.OnProcess := aOnProcess; - task.Secs := aOnProcessSecs; - task.NextTix := GetTickCount64+(aOnProcessSecs*1000-TIXPRECISION); - fTaskLock.Lock; - try - found := Find(TMethod(aOnProcess)); - if found>=0 then - fTask[found] := task else - fTasks.Add(task); - finally - fTaskLock.UnLock; - end; -end; - -function TSynBackgroundTimer.Processing: boolean; -begin - {$ifdef NOVARIANTS} - with fTaskLock.Padding[0] do - result := (VType=varBoolean) and VBoolean; - {$else} - result := fTaskLock.LockedBool[0]; - {$endif} -end; - -procedure TSynBackgroundTimer.WaitUntilNotProcessing(timeoutsecs: integer); -var timeout: Int64; -begin - timeout := GetTickCount64+timeoutsecs*1000; - while Processing and (GetTickcount64=0 then begin - with fTask[found] do begin - if aExecuteNow then - NextTix := 0; - if aMsg<>#0 then - AddRawUTF8(FIFO,aMsg); - end; - if aExecuteNow then - ProcessEvent.SetEvent; - result := true; - end; - finally - fTaskLock.UnLock; - end; -end; - -function TSynBackgroundTimer.DeQueue(aOnProcess: TOnSynBackgroundTimerProcess; - const aMsg: RawUTF8): boolean; -var found: integer; -begin - result := false; - if (self=nil) or Terminated or not Assigned(aOnProcess) then - exit; - fTaskLock.Lock; - try - found := Find(TMethod(aOnProcess)); - if found>=0 then - with fTask[found] do - result := DeleteRawUTF8(FIFO,FindRawUTF8(FIFO,aMsg)); - finally - fTaskLock.UnLock; - end; -end; - -function TSynBackgroundTimer.Disable(aOnProcess: TOnSynBackgroundTimerProcess): boolean; -var found: integer; -begin - result := false; - if (self=nil) or Terminated or not Assigned(aOnProcess) then - exit; - fTaskLock.Lock; - try - found := Find(TMethod(aOnProcess)); - if found>=0 then begin - fTasks.Delete(found); - result := true; - end; - finally - fTaskLock.UnLock; - end; -end; - - -{ TProcessInfo } - -{$ifdef MSWINDOWS} -function TProcessInfo.Init: boolean; -begin - FillCharFast(self,SizeOf(self),0); - result := Assigned(GetSystemTimes) and Assigned(GetProcessTimes) and - Assigned(GetProcessMemoryInfo); // no monitoring API under oldest Windows -end; - -function TProcessInfo.Start: boolean; -var ftidl,ftkrn,ftusr: TFileTime; - sidl,skrn,susr: Int64; -begin - result := Assigned(GetSystemTimes) and GetSystemTimes(ftidl,ftkrn,ftusr); - if not result then - exit; - FileTimeToInt64(ftidl,sidl); - FileTimeToInt64(ftkrn,skrn); - FileTimeToInt64(ftusr,susr); - fDiffIdle := sidl-fSysPrevIdle; - fDiffKernel := skrn-fSysPrevKernel; - fDiffUser := susr-fSysPrevUser; - fDiffTotal := fDiffKernel+fDiffUser; // kernel time also includes idle time - dec(fDiffKernel, fDiffIdle); - fSysPrevIdle := sidl; - fSysPrevKernel := skrn; - fSysPrevUser := susr; -end; - -function TProcessInfo.PerProcess(PID: cardinal; Now: PDateTime; - out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean; -var - h: THandle; - ftkrn,ftusr,ftp,fte: TFileTime; - pkrn,pusr: Int64; - mem: TProcessMemoryCounters; -begin - result := false; - FillCharFast(Data,SizeOf(Data),0); - h := OpenProcess(OpenProcessAccess,false,PID); - if h<>0 then - try - if GetProcessTimes(h,ftp,fte,ftkrn,ftusr) then begin - if Now<>nil then - Data.Timestamp := Now^; - FileTimeToInt64(ftkrn,pkrn); - FileTimeToInt64(ftusr,pusr); - if (PrevKernel<>0) and (fDiffTotal>0) then begin - Data.Kernel := ((pkrn-PrevKernel)*100)/fDiffTotal; - Data.User := ((pusr-PrevUser)*100)/fDiffTotal; - end; - PrevKernel := pkrn; - PrevUser := pusr; - FillCharFast(mem,SizeOf(mem),0); - mem.cb := SizeOf(mem); - if GetProcessMemoryInfo(h,mem,SizeOf(mem)) then begin - Data.WorkKB := mem.WorkingSetSize shr 10; - Data.VirtualKB := mem.PagefileUsage shr 10; - end; - result := true; - end; - finally - CloseHandle(h); - end; -end; - -function TProcessInfo.PerSystem(out Idle,Kernel,User: currency): boolean; -begin - if fDiffTotal<=0 then begin - Idle := 0; - Kernel := 0; - User := 0; - result := false; - end else begin - Kernel := SimpleRoundTo2Digits((fDiffKernel*100)/fDiffTotal); - User := SimpleRoundTo2Digits((fDiffUser*100)/fDiffTotal); - Idle := 100-Kernel-User; // ensure sum is always 100% - result := true; - end; -end; -{$else} // not implemented yet (use /proc ?) -function TProcessInfo.Init: boolean; -begin - FillZero(self,SizeOf(self)); - result := false; -end; - -function TProcessInfo.Start: boolean; -begin - result := false; -end; - -function TProcessInfo.PerProcess(PID: cardinal; Now: PDateTime; - out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean; -begin - result := false; -end; - -function TProcessInfo.PerSystem(out Idle,Kernel,User: currency): boolean; -var P: PUTF8Char; - U, K, I, S: cardinal; -begin // see http://www.linuxhowtos.org/System/procstat.htm - result := false; - P := pointer(StringFromFile('/proc/stat', {nosize=}true)); - if P=nil then - exit; - U := GetNextItemCardinal(P,' '){=user}+GetNextItemCardinal(P,' '){=nice}; - K := GetNextItemCardinal(P,' '){=system}; - I := GetNextItemCardinal(P,' '){=idle}; - S := U+K+I; - Kernel := SimpleRoundTo2Digits((K*100)/S); - User := SimpleRoundTo2Digits((U*100)/S); - Idle := 100-Kernel-User; // ensure sum is always 100% - result := S<>0; -end; { TODO : use a diff approach for TProcessInfo.PerSystem on Linux } -{$endif MSWINDOWS} - - -{ TSystemUse } - -procedure TSystemUse.BackgroundExecute(Sender: TSynBackgroundTimer; - Event: TWaitResult; const Msg: RawUTF8); -var i: integer; - now: TDateTime; -begin - if (fProcess=nil) or (fHistoryDepth=0) or not fProcessInfo.Start then - exit; - fTimer := Sender; - now := NowUTC; - fSafe.Enter; - try - inc(fDataIndex); - if fDataIndex>=fHistoryDepth then - fDataIndex := 0; - for i := high(fProcess) downto 0 do // backwards for fProcesses.Delete(i) - with fProcess[i] do - if fProcessInfo.PerProcess(ID,@now,Data[fDataIndex],PrevKernel,PrevUser) then begin - if Assigned(fOnMeasured) then - fOnMeasured(ID,Data[fDataIndex]); - end else - if UnsubscribeProcessOnAccessError then - // if GetLastError=ERROR_INVALID_PARAMETER then - fProcesses.Delete(i); - finally - fSafe.Leave; - end; -end; - -procedure TSystemUse.OnTimerExecute(Sender: TObject); -begin - BackgroundExecute(nil,wrSignaled,''); -end; - -constructor TSystemUse.Create(const aProcessID: array of integer; - aHistoryDepth: integer); -var i: integer; -begin - inherited Create; - fSafe := TAutoLocker.Create; - fProcesses.Init(TypeInfo(TSystemUseProcessDynArray),fProcess); - {$ifdef MSWINDOWS} - if not Assigned(GetSystemTimes) or not Assigned(GetProcessTimes) or - not Assigned(GetProcessMemoryInfo) then - exit; // no system monitoring API under oldest Windows - {$else} - exit; // not implemented yet - {$endif} - if aHistoryDepth<=0 then - aHistoryDepth := 1; - fHistoryDepth := aHistoryDepth; - SetLength(fProcess,length(aProcessID)); - for i := 0 to high(aProcessID) do begin - {$ifdef MSWINDOWS} - if aProcessID[i]=0 then - fProcess[i].ID := GetCurrentProcessID else - {$endif} - fProcess[i].ID := aProcessID[i]; - SetLength(fProcess[i].Data,fHistoryDepth); - end; -end; - -constructor TSystemUse.Create(aHistoryDepth: integer); -begin - Create([0],aHistoryDepth); -end; - -destructor TSystemUse.Destroy; -begin - inherited Destroy; - fSafe.Free; -end; - -procedure TSystemUse.Subscribe(aProcessID: integer); -var i,n: integer; -begin - if self=nil then - exit; - {$ifdef MSWINDOWS} - if aProcessID=0 then - aProcessID := GetCurrentProcessID; - {$endif} - fSafe.Enter; - try - n := length(fProcess); - for i := 0 to n-1 do - if fProcess[i].ID=aProcessID then - exit; // already subscribed - SetLength(fProcess,n+1); - fProcess[n].ID := aProcessID; - SetLength(fProcess[n].Data,fHistoryDepth); - finally - fSafe.Leave; - end; -end; - -function TSystemUse.Unsubscribe(aProcessID: integer): boolean; -var i: integer; -begin - result := false; - if self=nil then - exit; - fSafe.Enter; - try - i := ProcessIndex(aProcessID); - if i>=0 then begin - fProcesses.Delete(i); - result := true; - end; - finally - fSafe.Leave; - end; -end; - -function TSystemUse.ProcessIndex(aProcessID: integer): integer; -begin // caller should have made fSafe.Enter - {$ifdef MSWINDOWS} - if aProcessID=0 then - aProcessID := GetCurrentProcessID; - {$endif} - if self<>nil then - for result := 0 to high(fProcess) do - if fProcess[result].ID=aProcessID then - exit; - result := -1; -end; - -function TSystemUse.Data(out aData: TSystemUseData; aProcessID: integer=0): boolean; -var i: integer; -begin - result := false; - if self<>nil then begin - fSafe.Enter; - try - i := ProcessIndex(aProcessID); - if i>=0 then begin - with fProcess[i] do - aData := Data[fDataIndex]; - result := aData.Timestamp<>0; - if result then - exit; - end; - finally - fSafe.Leave; - end; - end; - FillCharFast(aData,SizeOf(aData),0); -end; - -function TSystemUse.Data(aProcessID: integer): TSystemUseData; -begin - Data(result,aProcessID); -end; - -function TSystemUse.KB(aProcessID: integer=0): cardinal; -begin - with Data(aProcessID) do - result := WorkKB+VirtualKB; -end; - -function TSystemUse.Percent(aProcessID: integer): single; -begin - with Data(aProcessID) do - result := Kernel+User; -end; - -function TSystemUse.PercentKernel(aProcessID: integer): single; -begin - result := Data(aProcessID).Kernel; -end; - -function TSystemUse.PercentUser(aProcessID: integer): single; -begin - result := Data(aProcessID).User; -end; - -function TSystemUse.PercentSystem(out Idle,Kernel,User: currency): boolean; -begin - result := fProcessInfo.PerSystem(Idle,Kernel,User); -end; - -function TSystemUse.HistoryData(aProcessID,aDepth: integer): TSystemUseDataDynArray; -var i,n,last: integer; -begin - result := nil; - if self=nil then - exit; - fSafe.Enter; - try - i := ProcessIndex(aProcessID); - if i>=0 then - with fProcess[i] do begin - n := length(Data); - last := n-1; - if (aDepth>0) and (n>aDepth) then - n := aDepth; - SetLength(result,n); // make ordered copy - for i := 0 to n-1 do begin - if i<=fDataIndex then - result[i] := Data[fDataIndex-i] else begin - result[i] := Data[last]; - dec(last); - end; - if PInt64(@result[i].Timestamp)^=0 then begin - SetLength(result,i); // truncate to latest available sample - break; - end; - end; - end; - finally - fSafe.Leave; - end; -end; - -function TSystemUse.History(aProcessID,aDepth: integer): TSingleDynArray; -var i,n: integer; - data: TSystemUseDataDynArray; -begin - data := HistoryData(aProcessID,aDepth); - n := length(data); - SetLength(result,n); - for i := 0 to n-1 do - result[i] := data[i].Kernel+data[i].User; -end; - -class function TSystemUse.Current(aCreateIfNone: boolean): TSystemUse; -begin - if (ProcessSystemUse=nil) and aCreateIfNone then - GarbageCollectorFreeAndNil(ProcessSystemUse,TSystemUse.Create(60)); - result := ProcessSystemUse; -end; - -function TSystemUse.HistoryText(aProcessID,aDepth: integer; - aDestMemoryMB: PRawUTF8): RawUTF8; -var data: TSystemUseDataDynArray; - mem: RawUTF8; - i: integer; -begin - result := ''; - data := HistoryData(aProcessID,aDepth); - {$ifdef LINUXNOTBSD} // https://www.retro11.de/ouxr/211bsd/usr/src/lib/libc/gen/getloadavg.c.html - if data = nil then - result := StringFromFile('/proc/loadavg',{HasNoSize=}true) else - {$endif LINUX} - for i := 0 to high(data) do - with data[i] do begin - result := FormatUTF8('%% ',[result,TruncTo2Digits(Kernel+User)]); - if aDestMemoryMB<>nil then - mem := FormatUTF8('%% ',[mem,TruncTo2Digits(WorkKB/1024)]); - end; - result := trim(result); - if aDestMemoryMB<>nil then - aDestMemoryMB^ := trim(mem); -end; - -{$ifndef NOVARIANTS} - -function TSystemUse.HistoryVariant(aProcessID,aDepth: integer): variant; -var res: TDocVariantData absolute result; - data: TSystemUseDataDynArray; - i: integer; -begin - VarClear(result); - data := HistoryData(aProcessID,aDepth); - res.InitFast(length(data),dvArray); - for i := 0 to high(data) do - res.AddItem(TruncTo2Digits(data[i].Kernel+data[i].User)); -end; - -{$endif NOVARIANTS} - - -{ TSynParallelProcess } - -constructor TSynParallelProcess.Create(ThreadPoolCount: integer; const ThreadName: RawUTF8; - OnBeforeExecute, OnAfterExecute: TNotifyThreadEvent; - MaxThreadPoolCount: integer); -var i: integer; -begin - inherited Create; - if ThreadPoolCount<0 then - raise ESynParallelProcess.CreateUTF8('%.Create(%,%)',[Self,ThreadPoolCount,ThreadName]); - if ThreadPoolCount>MaxThreadPoolCount then - ThreadPoolCount := MaxThreadPoolCount; - fThreadPoolCount := ThreadPoolCount; - fThreadName := ThreadName; - SetLength(fPool,fThreadPoolCount); - for i := 0 to fThreadPoolCount-1 do - fPool[i] := TSynParallelProcessThread.Create(nil,FormatUTF8('%#%/%', - [fThreadName,i+1,fThreadPoolCount]),OnBeforeExecute,OnAfterExecute); -end; - -destructor TSynParallelProcess.Destroy; -begin - ObjArrayClear(fPool); - inherited; -end; - -procedure TSynParallelProcess.ParallelRunAndWait(Method: TSynParallelProcessMethod; - MethodCount: integer); -var use,t,n,perthread: integer; - error: RawUTF8; -begin - if (MethodCount<=0) or not Assigned(Method) then - exit; - if (self=nil) or (MethodCount=1) or (fThreadPoolCount=0) then begin - Method(0,MethodCount-1); // no need (or impossible) to use background thread - exit; - end; - use := MethodCount; - if use>fThreadPoolCount+1 then // +1 to include current thread - use := fThreadPoolCount+1; - try - // start secondary threads - perthread := MethodCount div use; - if perthread=0 then - use := 1; - n := 0; - for t := 0 to use-2 do begin - repeat - case fPool[t].AcquireThread of - flagDestroying: // should not happen - raise ESynParallelProcess.CreateUTF8( - '%.ParallelRunAndWait [%] destroying',[self,fPool[t].fThreadName]); - flagIdle: - break; // acquired (should always be the case) - end; - Sleep(1); - until false; - fPool[t].Start(Method,n,n+perthread-1); - inc(n,perthread); - inc(fParallelRunCount); - end; - // run remaining items in the current thread - if n'' then - raise ESynParallelProcess.CreateUTF8('%.ParallelRunAndWait: %',[self,error]); - end; -end; - - -{ TBlockingProcess } - -constructor TBlockingProcess.Create(aTimeOutMs: integer; const aSafe: TSynLocker); -begin - inherited Create(nil,false,false,''); - if aTimeOutMs<=0 then - fTimeOutMs := 3000 else // never wait for ever - fTimeOutMs := aTimeOutMs; - fSafe := @aSafe; -end; - -constructor TBlockingProcess.Create(aTimeOutMs: integer); -begin - fOwnedSafe := TAutoLocker.Create; - Create(aTimeOutMS,fOwnedSafe.fSafe); -end; - -destructor TBlockingProcess.Destroy; -begin - fOwnedSafe.Free; - inherited Destroy; -end; - -function TBlockingProcess.WaitFor: TBlockingEvent; -begin - fSafe^.Lock; - try - result := fEvent; - if fEvent in [evRaised,evTimeOut] then - exit; - fEvent := evWaiting; - finally - fSafe^.UnLock; - end; - FixedWaitFor(self,fTimeOutMs); - fSafe^.Lock; - try - if fEvent<>evRaised then - fEvent := evTimeOut; - result := fEvent; - finally - fSafe^.UnLock; - end; -end; - -function TBlockingProcess.WaitFor(TimeOutMS: integer): TBlockingEvent; -begin - if TimeOutMS <= 0 then - fTimeOutMs := 3000 // never wait for ever - else - fTimeOutMs := TimeOutMS; - result := WaitFor; -end; - -function TBlockingProcess.NotifyFinished(alreadyLocked: boolean): boolean; -begin - result := false; - if not alreadyLocked then - fSafe^.Lock; - try - if fEvent in [evRaised,evTimeOut] then - exit; // ignore if already notified - fEvent := evRaised; - SetEvent; // notify caller to unlock "WaitFor" method - result := true; - finally - fSafe^.UnLock; - end; -end; - -procedure TBlockingProcess.ResetInternal; -begin - ResetEvent; - fEvent := evNone; -end; - -function TBlockingProcess.Reset: boolean; -begin - fSafe^.Lock; - try - result := fEvent<>evWaiting; - if result then - ResetInternal; - finally - fSafe^.UnLock; - end; -end; - -procedure TBlockingProcess.Lock; -begin - fSafe^.Lock; -end; - -procedure TBlockingProcess.Unlock; -begin - fSafe^.Unlock; -end; - - -{ TBlockingProcessPoolItem } - -procedure TBlockingProcessPoolItem.ResetInternal; -begin - inherited ResetInternal; // set fEvent := evNone - fCall := 0; -end; - - -{ TBlockingProcessPool } - -constructor TBlockingProcessPool.Create(aClass: TBlockingProcessPoolItemClass); -begin - inherited Create; - if aClass=nil then - fClass := TBlockingProcessPoolItem else - fClass := aClass; - fPool := TObjectListLocked.Create(true); -end; - -const - CALL_DESTROYING = -1; - -destructor TBlockingProcessPool.Destroy; -var i: integer; - someWaiting: boolean; -begin - fCallCounter := CALL_DESTROYING; - someWaiting := false; - for i := 0 to fPool.Count-1 do - with TBlockingProcessPoolItem(fPool.List[i]) do - if Event=evWaiting then begin - SetEvent; // release WaitFor (with evTimeOut) - someWaiting := true; - end; - if someWaiting then - sleep(10); // propagate the pending evTimeOut to the WaitFor threads - fPool.Free; - inherited; -end; - -function TBlockingProcessPool.NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem; -var i: integer; - p: ^TBlockingProcessPoolItem; -begin - result := nil; - if fCallCounter=CALL_DESTROYING then - exit; - if aTimeOutMs<=0 then - aTimeOutMs := 3000; // never wait for ever - fPool.Safe.Lock; - try - p := pointer(fPool.List); - for i := 1 to fPool.Count do - if p^.Call=0 then begin - result := p^; // found a non-used entry - result.fTimeOutMs := aTimeOutMS; - break; - end else - inc(p); - if result=nil then begin - result := fClass.Create(aTimeOutMS); - fPool.Add(result); - end; - inc(fCallCounter); // 1,2,3,... - result.fCall := fCallCounter; - finally - fPool.Safe.UnLock; - end; -end; - -function TBlockingProcessPool.FromCall(call: TBlockingProcessPoolCall; - locked: boolean): TBlockingProcessPoolItem; -var i: integer; - p: ^TBlockingProcessPoolItem; -begin - result := nil; - if (fCallCounter=CALL_DESTROYING) or (call<=0) then - exit; - fPool.Safe.Lock; - try - p := pointer(fPool.List); - for i := 1 to fPool.Count do - if p^.Call=call then begin - result := p^; - if locked then - result.Lock; - exit; - end else - inc(p); - finally - fPool.Safe.UnLock; - end; -end; - { MultiEvent* functions } @@ -64865,7 +62235,8 @@ procedure MultiEventRemove(var EventList; Index: Integer); max := length(Events); if cardinal(index)=64)and(MAX_SQLFIELDS<=256)); - {$warnings ON} + {$ifndef FPC}{$warnings ON}{$endif} Assert(SizeOf(THash128Rec)=SizeOf(THash128)); Assert(SizeOf(THash256Rec)=SizeOf(THash256)); Assert(SizeOf(TBlock128)=SizeOf(THash128)); diff --git a/ThirdParty/mORMot/Source/SynCrypto.pas b/ThirdParty/mORMot/Source/SynCrypto.pas index 99e64a80..c882c9b3 100644 --- a/ThirdParty/mORMot/Source/SynCrypto.pas +++ b/ThirdParty/mORMot/Source/SynCrypto.pas @@ -8,7 +8,7 @@ (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2018 Arnaud Bouchez + Synopse framework. Copyright (C) 2019 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -27,7 +27,7 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2018 + Portions created by the Initial Developer are Copyright (C) 2019 the Initial Developer. All Rights Reserved. Contributor(s): @@ -297,7 +297,8 @@ interface {$endif LVCL} Classes, SynLZ, // already included in SynCommons, and used by CompressShaAes() - SynCommons; + SynCommons, + SynTable; // for TSynUniqueIdentifierGenerator {$ifdef DELPHI5OROLDER} @@ -322,11 +323,11 @@ interface {$ifdef MSWINDOWS} {$define SHA512_X86} // external sha512-x86.obj/.o {$endif} - {$ifdef FPC_PIC} + {$ifdef ABSOLUTEPASCAL} {$define AES_PASCAL} // x86 AES asm below is not PIC-safe {$else} {$define CPUX86_NOTPIC} - {$endif FPC_PIC} + {$endif ABSOLUTEPASCAL} {$ifdef FPC} {$ifdef DARWIN} {$define AES_PASCAL} // as reported by alf @@ -427,7 +428,7 @@ {$ifdef UNICODE}THash128History = record{$else}THash128History = object{$endif // - this class will use VIA PadLock instructions, if available {$endif} // - we defined a record instead of a class, to allow stack allocation and - // thread-safe reuse of one initialized instance, if needed + // thread-safe reuse of one initialized instance (warning: not for Padlock) {$ifdef UNICODE}TAES = record{$else}TAES = object{$endif} private Context: packed array[1..AESContextSize] of byte; @@ -778,7 +779,7 @@ TAESAbstract = class(TSynPersistent) property IVHistoryDepth: integer read fIVHistoryDec.Depth write SetIVHistory; end; - /// handle AES cypher/uncypher with chaining + /// handle AES cypher/uncypher with chaining with out own optimized code // - use any of the inherited implementation, corresponding to the chaining // mode required - TAESECB, TAESCBC, TAESCFB, TAESOFB and TAESCTR classes to // handle in ECB, CBC, CFB, OFB and CTR mode (including PKCS7-like padding) @@ -790,11 +791,10 @@ TAESAbstractSyn = class(TAESAbstract) fIn, fOut: PAESBlock; fCV: TAESBlock; AES: TAES; - fCount: Cardinal; fAESInit: (initNone, initEncrypt, initDecrypt); procedure EncryptInit; procedure DecryptInit; - procedure TrailerBytes; + procedure TrailerBytes(count: cardinal); public /// creates a new instance with the very same values // - by design, our classes will use stateless context, so this method @@ -1074,13 +1074,10 @@ procedure AESIVCtrEncryptDecrypt(const BI; var BO; DoEncrypt: boolean); type /// thread-safe class containing a TAES encryption/decryption engine - TAESLocked = class(TSynPersistent) + TAESLocked = class(TSynPersistentLock) protected fAES: TAES; - fLock: TRTLCriticalSection; public - /// initialize the internal lock, but not the TAES instance - constructor Create; override; /// finalize all used memory and resources destructor Destroy; override; end; @@ -1268,8 +1265,9 @@ procedure SetMainAESPRNG; {$endif} /// low-level function returning some random binary using standard API -// - will call /dev/urandom under POSIX, and CryptGenRandom API on Windows, -// and fallback to SynCommons.FillRandom if the system is not supported +// - will call /dev/urandom or /dev/random under POSIX, and CryptGenRandom API +// on Windows, and fallback to SynCommons.FillRandom if the system API failed +// or for padding if more than 32 bytes is retrieved from /dev/urandom // - you should not have to call this procedure, but faster and safer TAESPRNG procedure FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: boolean); @@ -1277,7 +1275,7 @@ procedure FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: bool type PSHA1Digest = ^TSHA1Digest; /// 160 bits memory block for SHA-1 hash digest storage - TSHA1Digest = packed array[0..19] of byte; + TSHA1Digest = THash160; PSHA1 = ^TSHA1; /// handle SHA-1 hashing @@ -1538,9 +1536,6 @@ {$ifdef UNICODE}TSHA3 = record{$else}TSHA3 = object{$endif} procedure Done; end; - /// 64 bytes buffer, used internally during HMAC process - TByte64 = array[0..15] of cardinal; - TMD5In = array[0..15] of cardinal; PMD5In = ^TMD5In; /// 128 bits memory block for MD5 hash digest storage @@ -1700,19 +1695,6 @@ TAESWriteStream = class(TStream) procedure Finish; end; -/// overwrite a 64-byte buffer with zeros -// - may be used to cleanup stack-allocated content -// ! ... finally FillZero(temp); end; -procedure FillZero(var hash: TByte64); overload; - -/// overwrite a SHA-1 digest buffer with zeros -// - may be used to cleanup stack-allocated content -// ! ... finally FillZero(temp); end; -procedure FillZero(var hash: TSHA1Digest); overload; - -/// compare two SHA-1 digest buffers -function IsEqual(const A,B: TSHA1Digest): boolean; overload; - /// direct MD5 hash calculation of some data function MD5Buf(const Buffer; Len: Cardinal): TMD5Digest; @@ -1740,7 +1722,7 @@ function SHA512(const s: RawByteString): RawUTF8; {$ifdef UNICODE}THMAC_SHA1 = record{$else}THMAC_SHA1 = object{$endif} private sha: TSHA1; - step7data: TByte64; + step7data: THash512Rec; public /// prepare the HMAC authentication with the supplied key // - content of this record is stateless, so you can prepare a HMAC for a @@ -1915,7 +1897,7 @@ procedure SHA256Weak(const s: RawByteString; out Digest: TSHA256Digest); {$ifdef UNICODE}THMAC_SHA256 = record{$else}THMAC_SHA256 = object{$endif} private sha: TSha256; - step7data: TByte64; + step7data: THash512Rec; public /// prepare the HMAC authentication with the supplied key // - content of this record is stateless, so you can prepare a HMAC for a @@ -2145,7 +2127,7 @@ procedure HMAC_CRC256C(const key,msg: RawByteString; out result: THash256); over {$ifdef UNICODE}THMAC_CRC32C = record{$else}THMAC_CRC32C = object{$endif} private seed: cardinal; - step7data: TByte64; + step7data: THash512Rec; public /// prepare the HMAC authentication with the supplied key // - consider using Compute to re-use a prepared HMAC instance @@ -2386,16 +2368,16 @@ function AESSelfTest(onlytables: Boolean): boolean; /// self test of RC4 routines function RC4SelfTest: boolean; -/// entry point of the MD5 transform function - may be used from outside +/// entry point of the raw MD5 transform function - may be used for low-level use procedure RawMd5Compress(var Hash; Data: pointer); -/// entry point of the SHA-1 transform function - may be used from outside +/// entry point of the raw SHA-1 transform function - may be used for low-level use procedure RawSha1Compress(var Hash; Data: pointer); -/// entry point of the SHA-256 transform function - may be used from outside +/// entry point of the raw SHA-256 transform function - may be used for low-level use procedure RawSha256Compress(var Hash; Data: pointer); -/// entry point of the SHA-512 transform function - may be used from outside +/// entry point of the raw SHA-512 transform function - may be used for low-level use procedure RawSha512Compress(var Hash; Data: pointer); // little endian fast conversion @@ -3277,7 +3259,7 @@ procedure ComputeAesStaticTables; {$ifdef CPU32} -procedure bswap256(s,d: PIntegerArray); +procedure bswap256(s,d: PIntegerArray); {$ifdef FPC}nostackframe; assembler;{$endif} asm push ebx mov ecx,eax // ecx=s, edx=d @@ -3288,7 +3270,7 @@ procedure bswap256(s,d: PIntegerArray); pop ebx end; -procedure bswap160(s,d: PIntegerArray); +procedure bswap160(s,d: PIntegerArray); {$ifdef FPC}nostackframe; assembler;{$endif} asm push ebx mov ecx,eax // ecx=s, edx=d @@ -3473,40 +3455,22 @@ function SHA512(const s: RawByteString): RawUTF8; { THMAC_SHA1 } -procedure FillZero(var hash: TSHA1Digest); overload; -begin - FillCharFast(hash,sizeof(hash),0); -end; - -procedure FillZero(var hash: TByte64); -begin - FillCharFast(hash,sizeof(hash),0); -end; - -function IsEqual(const A,B: TSHA1Digest): boolean; -var a_: TIntegerArray absolute A; - b_: TIntegerArray absolute B; -begin // uses anti-forensic time constant "xor/or" pattern - result := ((a_[0] xor b_[0]) or (a_[1] xor b_[1]) or (a_[2] xor b_[2]) or - (a_[3] xor b_[3]) or (a_[4] xor b_[4]))=0; -end; - procedure THMAC_SHA1.Init(key: pointer; keylen: integer); var i: integer; - k0,k0xorIpad: TByte64; + k0,k0xorIpad: THash512Rec; begin - FillZero(k0); + FillZero(k0.b); if keylen>sizeof(k0) then - sha.Full(key,keylen,PSHA1Digest(@k0)^) else + sha.Full(key,keylen,k0.b160) else MoveFast(key^,k0,keylen); for i := 0 to 15 do - k0xorIpad[i] := k0[i] xor $36363636; + k0xorIpad.c[i] := k0.c[i] xor $36363636; for i := 0 to 15 do - step7data[i] := k0[i] xor $5c5c5c5c; + step7data.c[i] := k0.c[i] xor $5c5c5c5c; sha.Init; sha.Update(@k0xorIpad,sizeof(k0xorIpad)); - FillZero(k0); - FillZero(k0xorIpad); + FillZero(k0.b); + FillZero(k0xorIpad.b); end; procedure THMAC_SHA1.Update(msg: pointer; msglen: integer); @@ -3521,7 +3485,7 @@ procedure THMAC_SHA1.Done(out result: TSHA1Digest; NoInit: boolean); sha.Update(@result,sizeof(result)); sha.Final(result,NoInit); if not NoInit then - FillZero(step7data); + FillZero(step7data.b); end; procedure THMAC_SHA1.Done(out result: RawUTF8; NoInit: boolean); @@ -3587,20 +3551,20 @@ procedure PBKDF2_HMAC_SHA1(const password,salt: RawByteString; count: Integer; procedure THMAC_SHA256.Init(key: pointer; keylen: integer); var i: integer; - k0,k0xorIpad: TByte64; + k0,k0xorIpad: THash512Rec; begin - FillZero(k0); + FillZero(k0.b); if keylen>sizeof(k0) then - sha.Full(key,keylen,PSHA256Digest(@k0)^) else + sha.Full(key,keylen,k0.Lo) else MoveFast(key^,k0,keylen); for i := 0 to 15 do - k0xorIpad[i] := k0[i] xor $36363636; + k0xorIpad.c[i] := k0.c[i] xor $36363636; for i := 0 to 15 do - step7data[i] := k0[i] xor $5c5c5c5c; + step7data.c[i] := k0.c[i] xor $5c5c5c5c; sha.Init; sha.Update(@k0xorIpad,sizeof(k0xorIpad)); - FillZero(k0); - FillZero(k0xorIpad); + FillZero(k0.b); + FillZero(k0xorIpad.b); end; procedure THMAC_SHA256.Update(msg: pointer; msglen: integer); @@ -3630,7 +3594,7 @@ procedure THMAC_SHA256.Done(out result: TSHA256Digest; NoInit: boolean); sha.Update(@result,sizeof(result)); sha.Final(result,NoInit); if not NoInit then - FillZero(step7data); + FillZero(step7data.b); end; procedure THMAC_SHA256.Done(out result: RawUTF8; NoInit: boolean); @@ -3957,16 +3921,16 @@ procedure crc256cmix(h1,h2: cardinal; h: PCardinalArray); procedure HMAC_CRC256C(key,msg: pointer; keylen,msglen: integer; out result: THash256); var i: integer; h1,h2: cardinal; - k0,k0xorIpad,step7data: TByte64; + k0,k0xorIpad,step7data: THash512Rec; begin FillCharFast(k0,sizeof(k0),0); if keylen>sizeof(k0) then - crc256c(key,keylen,PHash256(@k0)^) else + crc256c(key,keylen,k0.Lo) else MoveFast(key^,k0,keylen); for i := 0 to 15 do - k0xorIpad[i] := k0[i] xor $36363636; + k0xorIpad.c[i] := k0.c[i] xor $36363636; for i := 0 to 15 do - step7data[i] := k0[i] xor $5c5c5c5c; + step7data.c[i] := k0.c[i] xor $5c5c5c5c; h1 := crc32c(crc32c(0,@k0xorIpad,sizeof(k0xorIpad)),msg,msglen); h2 := crc32c(crc32c(h1,@k0xorIpad,sizeof(k0xorIpad)),msg,msglen); crc256cmix(h1,h2,@result); @@ -3998,16 +3962,16 @@ procedure THMAC_CRC32C.Init(const key: RawByteString); procedure THMAC_CRC32C.Init(key: pointer; keylen: integer); var i: integer; - k0,k0xorIpad: TByte64; + k0,k0xorIpad: THash512Rec; begin FillCharFast(k0,sizeof(k0),0); if keylen>sizeof(k0) then - crc256c(key,keylen,PHash256(@k0)^) else + crc256c(key,keylen,k0.Lo) else MoveFast(key^,k0,keylen); for i := 0 to 15 do - k0xorIpad[i] := k0[i] xor $36363636; + k0xorIpad.c[i] := k0.c[i] xor $36363636; for i := 0 to 15 do - step7data[i] := k0[i] xor $5c5c5c5c; + step7data.c[i] := k0.c[i] xor $5c5c5c5c; seed := crc32c(0,@k0xorIpad,sizeof(k0xorIpad)); FillCharFast(k0,sizeof(k0),0); FillCharFast(k0xorIpad,sizeof(k0xorIpad),0); @@ -4836,11 +4800,7 @@ procedure aesencryptx64(const ctxt: TAESContext; bi, bo: PWA4); sub r13, 1 add r12, 16 lea r14, [rip+Te0] - {$ifdef FPC} - align 16 - {$else} - nop; nop; nop; nop; nop; nop - {$endif} + {$ifdef FPC} align 16 {$else} .align 16 {$endif} @round: mov esi, eax mov edi, edx movzx r8d, al @@ -6476,15 +6436,9 @@ procedure Sha256ExpandMessageBlocks(W, Buf: PIntegerArray); // optimized unrolled version from Intel's sha256_sse4.asm // Original code is released as Copyright (c) 2012, Intel Corporation var - K256Aligned: RawByteString; // movdqa + paddd do expect 16 bytes alignment - + K256AlignedStore: RawByteString; + K256Aligned: pointer; // movdqa + paddd do expect 16 bytes alignment const - PSHUFFLE_BYTE_FLIP_MASK: array[0..1] of QWord = - (QWord($0405060700010203),QWord($0C0D0E0F08090A0B)); - _SHUF_00BA: array[0..1] of QWord = - (QWord($0B0A090803020100),QWord($FFFFFFFFFFFFFFFF)); - _SHUF_DC00: array[0..1] of QWord = - (QWord($FFFFFFFFFFFFFFFF),QWord($B0A090803020100)); STACK_SIZE = 32{$ifndef LINUX}+7*16{$endif}; procedure sha256_sse4(var input_data; var digest; num_blks: PtrUInt); @@ -6527,9 +6481,9 @@ procedure sha256_sse4(var input_data; var digest; num_blks: PtrUInt); mov r9d,[rdx+14H] mov r10d,[rdx+18H] mov r11d,[rdx+1CH] - movdqu xmm12,[rip+PSHUFFLE_BYTE_FLIP_MASK] - movdqu xmm10,[rip+_SHUF_00BA] - movdqu xmm11,[rip+_SHUF_DC00] + movdqa xmm12,[rip+@flip] + movdqa xmm10,[rip+@00BA] + movdqa xmm11,[rip+@DC00] @loop0: mov rbp,[rip+K256Aligned] movdqu xmm4,[rcx] pshufb xmm4,xmm12 @@ -6541,7 +6495,6 @@ procedure sha256_sse4(var input_data; var digest; num_blks: PtrUInt); pshufb xmm7,xmm12 mov [rsp+8H],rcx mov rcx,3 - nop; nop; nop; nop; nop // manual align 16 @loop1: movdqa xmm9,[rbp] paddd xmm9,xmm4 movdqa [rsp+10H],xmm9 @@ -7434,10 +7387,18 @@ procedure sha256_sse4(var input_data; var digest; num_blks: PtrUInt); pop rsi {$endif} pop rbx + ret +{$ifdef FPC} align 16 {$else} .align 16 {$endif} +@flip: dq $0405060700010203 + dq $0C0D0E0F08090A0B +@00BA: dq $0B0A090803020100 + dq $FFFFFFFFFFFFFFFF +@DC00: dq $FFFFFFFFFFFFFFFF + dq $0B0A090803020100 end; {$endif CPUX64} -procedure sha256Compress(var Hash: TSHAHash; Data: pointer); +procedure Sha256CompressPas(var Hash: TSHAHash; Data: pointer); // Actual hashing function var H: TSHAHash; W: array[0..63] of cardinal; @@ -7446,16 +7407,6 @@ procedure sha256Compress(var Hash: TSHAHash; Data: pointer); t1, t2: cardinal; {$endif} begin - {$ifdef CPUX64} - if cfSSE41 in CpuFeatures then begin - if K256Aligned='' then - SetString(K256Aligned,PAnsiChar(@K256),SizeOf(K256)); - if PtrUInt(K256ALigned)and 15=0 then begin - sha256_sse4(Data^,Hash,1); - exit; - end; // if K256Aligned[] is not properly aligned -> fallback to pascal - end; - {$endif CPUX64} // calculate "expanded message blocks" Sha256ExpandMessageBlocks(@W,Data); // assign old working hash to local variables A..H @@ -7563,7 +7514,11 @@ procedure sha256Compress(var Hash: TSHAHash; Data: pointer); procedure RawSha256Compress(var Hash; Data: pointer); begin - sha256Compress(TSHAHash(Hash), Data); + {$ifdef CPUX64} + if K256AlignedStore<>'' then // use optimized Intel's sha256_sse4.asm + sha256_sse4(Data^,Hash,1) else + {$endif CPUX64} + Sha256CompressPas(TSHAHash(Hash),Data); end; procedure TSHA256.Final(out Digest: TSHA256Digest; NoInit: boolean); @@ -7575,14 +7530,14 @@ procedure TSHA256.Final(out Digest: TSHA256Digest; NoInit: boolean); FillcharFast(Data.Buffer[Data.Index+1],63-Data.Index,0); // compress if more than 448 bits (no space for 64 bit length storage) if Data.Index>=56 then begin - sha256Compress(Data.Hash,@Data.Buffer); + RawSha256Compress(Data.Hash,@Data.Buffer); FillcharFast(Data.Buffer,56,0); end; // write 64 bit Buffer length into the last bits of the last block // (in big endian format) and do a final compress PInteger(@Data.Buffer[56])^ := bswap32(TQWordRec(Data.MLen).H); PInteger(@Data.Buffer[60])^ := bswap32(TQWordRec(Data.MLen).L); - sha256Compress(Data.Hash,@Data.Buffer); + RawSha256Compress(Data.Hash,@Data.Buffer); // Hash -> Digest to little endian format bswap256(@Data.Hash,@Digest); // clear Data and internally stored Digest @@ -7637,10 +7592,10 @@ procedure TSHA256.Update(Buffer: pointer; Len: integer); if aLen<=Len then begin if Data.Index<>0 then begin MoveFast(Buffer^,Data.Buffer[Data.Index],aLen); - sha256Compress(Data.Hash,@Data.Buffer); + RawSha256Compress(Data.Hash,@Data.Buffer); Data.Index := 0; end else - sha256Compress(Data.Hash,Buffer); // avoid temporary copy + RawSha256Compress(Data.Hash,Buffer); // avoid temporary copy dec(Len,aLen); inc(PtrInt(Buffer),aLen); end else begin @@ -7801,6 +7756,19 @@ procedure sha512_compress(state: PQWord; block: PByteArray); cdecl; external; procedure sha512_sse4(data, hash: pointer; blocks: Int64); {$ifdef FPC}cdecl;{$endif} external; {$endif SHA512_X64} +procedure RawSha512Compress(var Hash; Data: pointer); +begin + {$ifdef SHA512_X86} + if cfSSSE3 in CpuFeatures then + sha512_compress(@Hash,Data) else + {$endif} + {$ifdef SHA512_X64} + if cfSSE41 in CpuFeatures then + sha512_sse4(Data,@Hash,1) else + {$endif} + sha512_compresspas(TSHA512Hash(Hash), Data); +end; + { TSHA384 } @@ -7809,28 +7777,12 @@ procedure TSHA384.Final(out Digest: TSHA384Digest; NoInit: boolean); Data[Index] := $80; FillcharFast(Data[Index+1],127-Index,0); if Index>=112 then begin - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,@Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(@Data,@Hash,1) else - {$endif} - sha512_compresspas(Hash,@Data); + RawSha512Compress(Hash,@Data); FillcharFast(Data,112,0); end; PQWord(@Data[112])^ := bswap64(MLen shr 61); PQWord(@Data[120])^ := bswap64(MLen shl 3); - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,@Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(@Data,@Hash,1) else - {$endif} - sha512_compresspas(Hash,@Data); + RawSha512Compress(Hash,@Data); bswap64array(@Hash,@Digest,6); if not NoInit then Init; @@ -7873,26 +7825,10 @@ procedure TSHA384.Update(Buffer: pointer; Len: integer); if aLen<=Len then begin if Index<>0 then begin MoveFast(Buffer^,Data[Index],aLen); - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,@Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(@Data,@Hash,1) else - {$endif} - sha512_compresspas(Hash,@Data); + RawSha512Compress(Hash,@Data); Index := 0; end else // avoid temporary copy - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,Buffer) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(Buffer,@Hash,1) else - {$endif} - sha512_compresspas(Hash,Buffer); + RawSha512Compress(Hash,Buffer); dec(Len,aLen); inc(PByte(Buffer),aLen); end else begin @@ -7916,28 +7852,12 @@ procedure TSHA512.Final(out Digest: TSHA512Digest; NoInit: boolean); Data[Index] := $80; FillcharFast(Data[Index+1],127-Index,0); if Index>=112 then begin - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,@Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(@Data,@Hash,1) else - {$endif} - sha512_compresspas(Hash,@Data); + RawSha512Compress(Hash,@Data); FillcharFast(Data,112,0); end; PQWord(@Data[112])^ := bswap64(MLen shr 61); PQWord(@Data[120])^ := bswap64(MLen shl 3); - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,@Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(@Data,@Hash,1) else - {$endif} - sha512_compresspas(Hash,@Data); + RawSha512Compress(Hash,@Data); bswap64array(@Hash,@Digest,8); if not NoInit then Init; @@ -7980,26 +7900,10 @@ procedure TSHA512.Update(Buffer: pointer; Len: integer); if aLen<=Len then begin if Index<>0 then begin MoveFast(Buffer^,Data[Index],aLen); - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,@Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(@Data,@Hash,1) else - {$endif} - sha512_compresspas(Hash,@Data); + RawSha512Compress(Hash,@Data); Index := 0; end else // avoid temporary copy - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,Buffer) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(Buffer,@Hash,1) else - {$endif} - sha512_compresspas(Hash,Buffer); + RawSha512Compress(Hash,Buffer); dec(Len,aLen); inc(PByte(Buffer),aLen); end else begin @@ -8010,19 +7914,6 @@ procedure TSHA512.Update(Buffer: pointer; Len: integer); until Len<=0; end; -procedure RawSha512Compress(var Hash; Data: pointer); -begin - {$ifdef SHA512_X86} - if cfSSSE3 in CpuFeatures then - sha512_compress(@Hash,Data) else - {$endif} - {$ifdef SHA512_X64} - if cfSSE41 in CpuFeatures then - sha512_sse4(Data,@Hash,1) else - {$endif} - sha512_compresspas(TSHA512Hash(Hash), Data); -end; - procedure TSHA512.Update(const Buffer: RawByteString); begin Update(pointer(Buffer),length(Buffer)); @@ -8189,6 +8080,7 @@ procedure KeccakPermutation(A: PQWordArray); procedure KeccakPermutationKernel(B, A, C: Pointer); {$ifdef CPU32} // Eric Grange's MMX version (PIC-safe) +{$ifdef FPC}nostackframe; assembler;{$endif} asm add edx, 128 add eax, 128 @@ -8530,7 +8422,7 @@ procedure KeccakPermutationKernel(B, A, C: Pointer); movq [edx + 64], mm0 {$else} {$ifdef FPC}nostackframe; assembler; asm{$else} -// Synopse's x64 asm, optimized for both in/out-order pipelined CPUs +// Synopse's x64 asm, optimized for both in+out-order pipelined CPUs asm // input: rcx=B, rdx=A, r8=C (Linux: rdi,rsi,rdx) .noframe {$endif}{$ifndef win64} @@ -9385,9 +9277,9 @@ procedure TSynSigner.Update(aBuffer: pointer; aLen: integer); procedure TSynSigner.Final(out aSignature: THash512Rec; aNoInit: boolean); begin case fAlgo of - saSha1: PHMAC_SHA1(@ctxt)^.Done(PSHA1Digest(@aSignature)^,aNoInit); + saSha1: PHMAC_SHA1(@ctxt)^.Done(aSignature.b160,aNoInit); saSha256: PHMAC_SHA256(@ctxt)^.Done(aSignature.Lo,aNoInit); - saSha384: PHMAC_SHA384(@ctxt)^.Done(aSignature.b3,aNoInit); + saSha384: PHMAC_SHA384(@ctxt)^.Done(aSignature.b384,aNoInit); saSha512: PHMAC_SHA512(@ctxt)^.Done(aSignature.b,aNoInit); saSha3224..saSha3S256: PSHA3(@ctxt)^.Final(@aSignature,fSignatureSize shl 3,aNoInit); end; @@ -10117,7 +10009,7 @@ procedure XorBlock(p: PIntegerArray; Count, Cod: integer); inc(PByte(p),16); end; Cod := (Cod shl 11) xor integer(Td0[cod shr 21]); - for i := 1 to (Count and 15)shr 2 do begin // last 4 bytes blocs + for i := 1 to (Count and AESBlockMod)shr 2 do begin // last 4 bytes blocs p^[0] := p^[0] xor Cod; inc(PByte(p),4); end; @@ -10160,7 +10052,7 @@ procedure XorConst(P: PIntegerArray; Count: integer); P^[3] := P^[3] xor Code; inc(PByte(P),16); end; - for i := 0 to ((Count and 15)shr 2)-1 do // last 4 bytes blocs + for i := 0 to ((Count and AESBlockMod)shr 2)-1 do // last 4 bytes blocs P^[i] := P^[i] xor Code; end; @@ -12109,9 +12001,6 @@ procedure TSHA1.Update(const Buffer: RawByteString); { TAESAbstract } -const - sAESException = 'AES engine initialization failure'; - var aesivctr: array[boolean] of TAESLocked; @@ -12125,17 +12014,16 @@ procedure AESIVCtrEncryptDecrypt(const BI; var BO; DoEncrypt: boolean); DecryptInit(AESIVCTR_KEY,128); end; with aesivctr[DoEncrypt] do begin - EnterCriticalSection(fLock); + fSafe^.Lock; TAESContext(fAES.Context).DoBlock(fAES.Context,BI,BO); - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; end; constructor TAESAbstract.Create(const aKey; aKeySize: cardinal); begin if (aKeySize<>128) and (aKeySize<>192) and (aKeySize<>256) then - raise ESynCrypto.CreateUTF8( - '%.Create key size = %; should be either 128, 192 or 256',[self,aKeySize]); + raise ESynCrypto.CreateUTF8('%.Create(aKeySize=%): 128/192/256 required',[self,aKeySize]); fKeySize := aKeySize; fKeySizeBytes := fKeySize shr 3; MoveFast(aKey,fKey,fKeySizeBytes); @@ -12536,21 +12424,18 @@ destructor TAESAbstractSyn.Destroy; function TAESAbstractSyn.Clone: TAESAbstract; begin - {$ifdef USEPADLOCK} - if TAESContext(AES).initialized and (TAESContext(AES).ViaCtx<>nil) then begin - result := inherited Clone; - exit; + if (fIVHistoryDec.Count<>0) {$ifdef USEPADLOCK} or + TAESContext(AES).initialized and (TAESContext(AES).ViaCtx<>nil){$endif} then + result := inherited Clone else begin + result := NewInstance as TAESAbstractSyn; + MoveFast(pointer(self)^,pointer(result)^,InstanceSize); end; - {$endif} - result := NewInstance as TAESAbstractSyn; - MoveFast(pointer(self)^,pointer(result)^,InstanceSize); end; procedure TAESAbstractSyn.Decrypt(BufIn, BufOut: pointer; Count: cardinal); begin fIn := BufIn; fOut := BufOut; - fCount := Count; fCV := fIV; end; @@ -12558,14 +12443,13 @@ procedure TAESAbstractSyn.DecryptInit; begin if AES.DecryptInit(fKey,fKeySize) then fAESInit := initDecrypt else - raise ESynCrypto.Create(sAESException); + raise ESynCrypto.CreateUTF8('%.DecryptInit',[self]); end; procedure TAESAbstractSyn.Encrypt(BufIn, BufOut: pointer; Count: cardinal); begin fIn := BufIn; fOut := BufOut; - fCount := Count; fCV := fIV; end; @@ -12573,15 +12457,15 @@ procedure TAESAbstractSyn.EncryptInit; begin if AES.EncryptInit(fKey,fKeySize) then fAESInit := initEncrypt else - raise ESynCrypto.Create(sAESException); + raise ESynCrypto.CreateUTF8('%.EncryptInit',[self]); end; -procedure TAESAbstractSyn.TrailerBytes; +procedure TAESAbstractSyn.TrailerBytes(count: cardinal); begin if fAESInit<>initEncrypt then EncryptInit; TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); - XorMemory(pointer(fOut),pointer(fIn),@fCV,fCount); + XorMemory(pointer(fOut),pointer(fIn),@fCV,count); end; @@ -12590,7 +12474,7 @@ procedure TAESAbstractSyn.TrailerBytes; procedure TAESECB.Decrypt(BufIn, BufOut: pointer; Count: cardinal); var i: integer; begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut if fAESInit<>initDecrypt then DecryptInit; for i := 1 to Count shr 4 do begin @@ -12598,15 +12482,15 @@ procedure TAESECB.Decrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; procedure TAESECB.Encrypt(BufIn, BufOut: pointer; Count: cardinal); var i: integer; begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut if fAESInit<>initEncrypt then EncryptInit; for i := 1 to Count shr 4 do begin @@ -12614,9 +12498,9 @@ procedure TAESECB.Encrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; @@ -12626,7 +12510,7 @@ procedure TAESCBC.Decrypt(BufIn, BufOut: pointer; Count: cardinal); var i: integer; tmp: TAESBlock; begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut if Count>=sizeof(TAESBlock) then begin if fAESInit<>initDecrypt then DecryptInit; @@ -12639,15 +12523,15 @@ procedure TAESCBC.Decrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fOut); end; end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; procedure TAESCBC.Encrypt(BufIn, BufOut: pointer; Count: cardinal); var i: integer; begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut if fAESInit<>initEncrypt then EncryptInit; for i := 1 to Count shr 4 do begin @@ -12657,9 +12541,9 @@ procedure TAESCBC.Encrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; { TAESAbstractEncryptOnly } @@ -12731,7 +12615,7 @@ procedure TAESCFB.Decrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin tmp := fIn^; TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); @@ -12740,9 +12624,9 @@ procedure TAESCFB.Decrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; end; @@ -12780,7 +12664,7 @@ procedure TAESCFB.Encrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); XorBlock16(pointer(fIn),pointer(fOut),pointer(@fCV)); @@ -12788,9 +12672,9 @@ procedure TAESCFB.Encrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; end; @@ -12883,7 +12767,7 @@ procedure TAESCFBCRC.Decrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin tmp := fIn^; crcblock(@fMAC.encrypted,pointer(fIn)); // fIn may be = fOut @@ -12894,11 +12778,11 @@ procedure TAESCFBCRC.Decrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then begin - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then begin + TrailerBytes(Count); with fMAC do // includes trailing bytes to the plain crc - PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fOut),fCount); + PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fOut),Count); end; end; end; @@ -12940,7 +12824,7 @@ procedure TAESCFBCRC.Encrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); crcblock(@fMAC.plain,pointer(fIn)); // fOut may be = fIn @@ -12950,11 +12834,11 @@ procedure TAESCFBCRC.Encrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then begin - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then begin with fMAC do // includes trailing bytes to the plain crc - PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fIn),fCount); + PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fIn),Count); + TrailerBytes(Count); end; end; end; @@ -12999,7 +12883,7 @@ procedure TAESOFBCRC.Decrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited Encrypt(BufIn,BufOut,Count); // CV := IV + set fIn,fOut,fCount + inherited Encrypt(BufIn,BufOut,Count); // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); crcblock(@fMAC.encrypted,pointer(fIn)); // fOut may be = fIn @@ -13008,11 +12892,11 @@ procedure TAESOFBCRC.Decrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then begin - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then begin + TrailerBytes(Count); with fMAC do // includes trailing bytes to the plain crc - PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fOut),fCount); + PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fOut),Count); end; end; end; @@ -13054,7 +12938,7 @@ procedure TAESOFBCRC.Encrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited Encrypt(BufIn,BufOut,Count); // CV := IV + set fIn,fOut,fCount + inherited Encrypt(BufIn,BufOut,Count); // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); crcblock(@fMAC.plain,pointer(fIn)); // fOut may be = fIn @@ -13063,11 +12947,11 @@ procedure TAESOFBCRC.Encrypt(BufIn, BufOut: pointer; Count: cardinal); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then begin - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then begin with fMAC do // includes trailing bytes to the plain crc - PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fIn),fCount); + PCardinal(@plain)^ := crc32c(PCardinal(@plain)^,pointer(fIn),Count); + TrailerBytes(Count); end; end; end; @@ -13131,6 +13015,7 @@ procedure AesNiEncryptOFB_128(self: TAESOFB; source, dest: pointer; blockcount: movdqu xmm9,[rdi+16*8] movdqu xmm10,[rdi+16*9] movdqu xmm11,[rdi+16*10] + {$ifdef FPC} align 16 {$else} .align 16 {$endif} @s: movdqu xmm15,dqword ptr [rsi] pxor xmm7,xmm0 aesenc xmm7,xmm1 @@ -13221,6 +13106,7 @@ procedure AesNiEncryptOFB_256(self: TAESOFB; source, dest: pointer; blockcount: movdqu xmm13,[rdi+16*12] movdqu xmm14,[rdi+16*13] add rdi, 16*14 + {$ifdef FPC} align 16 {$else} .align 16 {$endif} @s: movdqu xmm15,[rdi] pxor xmm7,xmm0 aesenc xmm7,xmm1 @@ -13302,16 +13188,16 @@ procedure TAESOFB.Encrypt(BufIn, BufOut: pointer; Count: cardinal); pxor xmm7,xmm7 // for safety end else {$endif} begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin TAESContext(AES.Context).DoBlock(AES.Context,fCV,fCV); XorBlock16(pointer(fIn),pointer(fOut),pointer(@fCV)); inc(fIn); inc(fOut); end; - fCount := fCount and AESBlockMod; - if fCount<>0 then - TrailerBytes; + Count := Count and AESBlockMod; + if Count<>0 then + TrailerBytes(Count); end; end; @@ -13327,7 +13213,7 @@ procedure TAESCTR.Encrypt(BufIn, BufOut: pointer; Count: cardinal); var i,j: integer; tmp: TAESBlock; begin - inherited; // CV := IV + set fIn,fOut,fCount + inherited; // CV := IV + set fIn,fOut for i := 1 to Count shr 4 do begin TAESContext(AES.Context).DoBlock(AES.Context,fCV,tmp); inc(fCV[7]); // counter is in the lower 64 bits, nonce in the upper 64 bits @@ -13539,7 +13425,7 @@ procedure TAESCBC_API.InternalSetMode; procedure TAESCFB_API.InternalSetMode; begin - raise ESynCrypto.Create('CRYPT_MODE_CFB does not work'); + raise ESynCrypto.CreateUTF8('%: CRYPT_MODE_CFB does not work',[self]); fInternalMode := CRYPT_MODE_CFB; end; @@ -13547,7 +13433,7 @@ procedure TAESCFB_API.InternalSetMode; procedure TAESOFB_API.InternalSetMode; begin - raise ESynCrypto.Create('CRYPT_MODE_OFB not implemented by PROV_RSA_AES'); + raise ESynCrypto.CreateUTF8('%: CRYPT_MODE_OFB not implemented by PROV_RSA_AES',[self]); fInternalMode := CRYPT_MODE_OFB; end; @@ -13558,17 +13444,10 @@ procedure TAESOFB_API.InternalSetMode; { TAESLocked } -constructor TAESLocked.Create; -begin - inherited Create; - InitializeCriticalSection(fLock); -end; - destructor TAESLocked.Destroy; begin inherited Destroy; fAES.Done; // mandatory for Padlock - also fill AES buffer with 0 for safety - DeleteCriticalSection(fLock); end; @@ -13604,10 +13483,9 @@ procedure FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: bool if dev>0 then try i := Len; - repeat - dec(i,FileRead(dev,Buffer^[Len-i],i)); - until i<=0; - fromos := i=0; + if i>32 then + i := 32; // up to 256 bits - see "man urandom" Usage paragraph + fromos := (FileRead(dev,Buffer[0],i)=i) and (Len<=32); // will XOR up to Len finally FileClose(dev); end; @@ -13622,8 +13500,8 @@ procedure FillSystemRandom(Buffer: PByteArray; Len: integer; AllowBlocking: bool if fromos then exit; i := Len; - repeat - SynCommons.FillRandom(@tmp,SizeOf(tmp) shr 2); // SynCommons as fallback + repeat // call Random32() (=RdRand32 or Lecuyer) as fallback/padding + SynCommons.FillRandom(@tmp,SizeOf(tmp) shr 2); if i<=SizeOf(tmp) then begin XorMemory(@Buffer^[Len-i],@tmp,i); break; @@ -13656,7 +13534,7 @@ class function TAESPRNG.GetEntropy(Len: integer; SystemOnly: boolean): RawByteSt try // retrieve some initial entropy from OS SetLength(fromos,Len); - FillSystemRandom(pointer(fromos),len,true); + FillSystemRandom(pointer(fromos),len,{allowblocking=}true); if SystemOnly then begin result := fromos; fromos := ''; @@ -13676,7 +13554,7 @@ class function TAESPRNG.GetEntropy(Len: integer; SystemOnly: boolean): RawByteSt data.i0 := integer(HInstance); // override data.d0d1/h0 data.i1 := integer(GetCurrentThreadId); data.i2 := integer(MainThreadID); - data.i3 := integer(UnixTimeUTC); + data.i3 := integer(UnixMSTimeUTC); SleepHiRes(0); // force non deterministic time shift sha3update; sha3.Update(OSVersionText); @@ -13695,11 +13573,14 @@ procedure TAESPRNG.Seed; try entropy := GetEntropy(128); // 128 bytes is the HMAC_SHA512 key block size PBKDF2_HMAC_SHA512(entropy,ExeVersion.User,fSeedPBKDF2Rounds,key.b); - EnterCriticalSection(fLock); - fAES.EncryptInit(key.Lo,fAESKeySize); - crcblocks(@fCTR,@key.Hi,2); - fBytesSinceSeed := 0; - LeaveCriticalSection(fLock); + fSafe^.Lock; + try + fAES.EncryptInit(key.Lo,fAESKeySize); + crcblocks(@fCTR,@key.Hi,2); + fBytesSinceSeed := 0; + finally + fSafe^.UnLock; + end; finally FillZero(key.b); // avoid the key appear in clear on stack FillZero(entropy); @@ -13729,12 +13610,12 @@ procedure TAESPRNG.FillRandom(out Block: TAESBlock); begin if fBytesSinceSeed>fSeedAfterBytes then Seed; - EnterCriticalSection(fLock); + fSafe^.Lock; TAESContext(fAES.Context).DoBlock(fAES.Context,fCTR.b,Block); IncrementCTR; inc(fBytesSinceSeed,SizeOf(Block)); inc(fTotalBytes,SizeOf(Block)); - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; procedure TAESPRNG.FillRandom(out Buffer: THash256); @@ -13751,7 +13632,7 @@ procedure TAESPRNG.FillRandom(Buffer: pointer; Len: integer); exit; if fBytesSinceSeed>fSeedAfterBytes then Seed; - EnterCriticalSection(fLock); + fSafe^.Lock; for i := 1 to Len shr 4 do begin TAESContext(fAES.Context).DoBlock(fAES.Context,fCTR.b,buf^); IncrementCTR; @@ -13765,7 +13646,7 @@ procedure TAESPRNG.FillRandom(Buffer: pointer; Len: integer); IncrementCTR; MoveFast(rnd,buf^,Len); end; - LeaveCriticalSection(fLock); + fSafe^.UnLock; end; function TAESPRNG.FillRandom(Len: integer): RawByteString; @@ -13813,19 +13694,11 @@ function TAESPRNG.Random64: QWord; end; function TAESPRNG.RandomExt: TSynExtended; -{$ifdef FPC_OR_UNICODE} -const coeff: double = (1.0/$100000000)/$100000000; // 2^-64 -{$else} // circumvent QWord bug on oldest Delphi revisions const coeff: double = (1.0/$80000000)/$100000000; // 2^-63 -{$endif} var block: THash128Rec; begin FillRandom(block.b); - {$ifdef FPC_OR_UNICODE} - result := (block.L xor block.H)*coeff; - {$else} - result := abs(block.Lo xor block.Hi)*coeff; - {$endif} + result := ((block.Lo xor block.Hi) and $7fffffffffffffff)*coeff; end; function TAESPRNG.RandomPassword(Len: integer): RawUTF8; @@ -14268,7 +14141,7 @@ function CryptDataForCurrentUserDPAPI(const Data,AppSecret: RawByteString; Encry end else result := ''; end; -{$endif} +{$endif MSWINDOWS} var __h: THash256; @@ -15060,7 +14933,7 @@ function crc32c_sse42_aesni(crc: cardinal; buf: PAnsiChar; len: cardinal): cardi ja @intel // only call Intel code if worth it shr r8, 3 jz @2 - {$ifdef FPC}align 8{$endif} + {$ifdef FPC} align 8 {$else} .align 8 {$endif} @1: {$ifdef FPC} crc32 rax, qword [rdx] // hash 8 bytes per opcode {$else} @@ -15111,6 +14984,14 @@ initialization if (cfSSE42 in CpuFeatures) and (cfAesNi in CpuFeatures) then crc32c := @crc32c_sse42_aesni; {$endif} +{$ifdef CPUX64} + if cfSSE41 in CpuFeatures then begin // optimized Intel's sha256_sse4.asm ? + if K256AlignedStore='' then + GetMemAligned(K256AlignedStore,@K256,SizeOf(K256),K256Aligned); + if PtrUInt(K256Aligned) and 15<>0 then + K256AlignedStore := ''; // if not properly aligned -> fallback to pascal + end; +{$endif CPUX64} TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType(TypeInfo(TSignAlgo)); TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TSynSignerParams), 'algo:TSignAlgo secret,salt:RawUTF8 rounds:integer'); diff --git a/ThirdParty/mORMot/Source/SynEcc.pas b/ThirdParty/mORMot/Source/SynEcc.pas index 2e4dd430..25cd4459 100644 --- a/ThirdParty/mORMot/Source/SynEcc.pas +++ b/ThirdParty/mORMot/Source/SynEcc.pas @@ -6,7 +6,7 @@ (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2018 Arnaud Bouchez + Synopse framework. Copyright (C) 2019 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2018 + Portions created by the Initial Developer are Copyright (C) 2019 the Initial Developer. All Rights Reserved. Contributor(s): @@ -91,19 +91,20 @@ interface uses {$ifdef MSWINDOWS} - Windows, // for CriticalSection API inling + Windows, // for CriticalSection API inling {$else} // for GetFileSize emulated API - {$ifdef KYLIX3} - SynKylix, - {$endif} - {$ifdef FPC} - SynFPCLinux, - {$endif} + {$ifdef KYLIX3} + SynKylix, + {$endif} + {$ifdef FPC} + SynFPCLinux, + {$endif} {$endif MSWINDOWS} SysUtils, Classes, Contnrs, SynCommons, + SynTable, SynCrypto; @@ -1259,7 +1260,7 @@ TECCSignatureCertifiedFile = class(TECCSignatureCertified) // based on JSON objects or even plain base-64 encoded JSON strings // - consider using TECCCertificateChainFile from mORMot.pas if you want // to use convenient human-readable JSON serialization in files - TECCCertificateChain = class(TSynPersistentLocked) + TECCCertificateChain = class(TSynPersistentLock) protected fItems: TECCCertificateObjArray; fIsValidCached: boolean; @@ -2086,6 +2087,12 @@ function _isZero(const VLI: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif} result := (VLI[0]=0) and (VLI[1]=0) and (VLI[2]=0) and (VLI[3]=0); end; +function _equals(const Left, Right: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif} +begin + result := (Left[0]=Right[0]) and (Left[1]=Right[1]) and (Left[2]=Right[2]) and + (Left[3]=Right[3]); +end; + // counts the number of bits required for VLI function _numBits(const VLI: TVLI): integer; var i: integer; @@ -2636,7 +2643,7 @@ procedure EccPointDoubleJacobian(var X1, Y1, Z1: TVLI); _modMult_fast(X1, X1, Z1); // t1 = x1^2 - z1^4 _modAdd(Z1, X1, X1, Curve_P_32); // t3 = 2*(x1^2 - z1^4) _modAdd(X1, X1, Z1, Curve_P_32); // t1 = 3*(x1^2 - z1^4) - if GetBit(X1, 0) then begin + if GetBitPtr(@X1, 0) then begin carry := _add(X1, X1, Curve_P_32); _rshift1(X1); X1[NUM_ECC_DIGITS-1] := X1[NUM_ECC_DIGITS-1] or (carry shl 63); @@ -2740,13 +2747,13 @@ procedure EccPointMult(out Output: TEccPoint; const Point: TEccPoint; Scalar: TV Ry[1] := Point.y; _XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], InitialZ); for i := _numBits(Scalar) - 2 downto 1 do begin - if GetBit(Scalar, i) then + if GetBitPtr(@Scalar, i) then nb := 0 else nb := 1; _XYcZ_addC(Rx[1-nb], Ry[1-nb], Rx[nb], Ry[nb]); _XYcZ_add(Rx[nb], Ry[nb], Rx[1-nb], Ry[1-nb]); end; - if GetBit(Scalar, 0) then + if GetBitPtr(@Scalar, 0) then nb := 0 else nb := 1; _XYcZ_addC(Rx[1-nb], Ry[1-nb], Rx[nb], Ry[nb]); @@ -2783,7 +2790,7 @@ procedure ModSqrt(var a: TVLI); _add(p1, Curve_P_32, _1); // p1 = curve_p + 1 for i := _numBits(p1) - 1 downto 2 do begin _modSquare_fast(result, result, false); - if GetBit(p1, i) then + if GetBitPtr(@p1, i) then _modMult_fast(result, result, a); end; a := result; @@ -2813,7 +2820,7 @@ function ecc_make_key_pas(out PublicKey: TECCPublicKey; out PrivateKey: TECCPriv TAESPRNG.Fill(THash256(PrivateK)); if tries >= MAX_TRIES then exit; - if _isZero(PrivateK) or (_cmp(PrivateK, _1) = 0) or (_cmp(PrivateK, _11) = 0) then + if _isZero(PrivateK) or _equals(PrivateK, _1) or _equals(PrivateK, _11) then continue; // Make sure the private key is in the range [1, n-1] // For the supported curves, n is always large enough that we only need @@ -2930,7 +2937,7 @@ function ecdsa_sign_pas(const PrivateKey: TECCPrivateKey; const Hash: TEccHash; TAESPRNG.Fill(THash256(k)); if Tries >= MAX_TRIES then exit; - if _isZero(k) or (_cmp(k, _1) = 0) or (_cmp(k, _11) = 0) then + if _isZero(k) or _equals(k, _1) or _equals(k, _11) then continue; if _cmp(Curve_N_32, k) <> 1 then _sub(k, k, Curve_N_32); @@ -2989,10 +2996,10 @@ function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed; Index := _numBits(u2); if Index > NumBits then NumBits := Index; - if GetBit(u1, NumBits-1) then + if GetBitPtr(@u1, NumBits-1) then Index := 1 else Index := 0; - if GetBit(u2, NumBits-1) then + if GetBitPtr(@u2, NumBits-1) then inc(Index, 2); Point := Points[Index]; rx := Point.x; @@ -3000,10 +3007,10 @@ function ecdsa_verify_pas(const PublicKey: TECCPublicKeyUncompressed; z := _1; for i := NumBits - 2 downto 0 do begin EccPointDoubleJacobian(rx, ry, z); - if GetBit(u1, i) then + if GetBitPtr(@u1, i) then Index := 1 else Index := 0; - if GetBit(u2, i) then + if GetBitPtr(@u2, i) then inc(Index, 2); Point := Points[Index]; if Point <> nil then begin @@ -3705,7 +3712,7 @@ function TECCCertificate.Encrypt(const Plain: RawByteString; finally FillZero(aeskey); FillZero(mackey); - FillcharFast(rndpriv,sizeof(rndpriv),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(rndpriv,sizeof(rndpriv),0); if dec<>Plain then FillZero(dec); if content<>Plain then @@ -4828,9 +4835,12 @@ function TECCCertificateChain.LoadFromJson(const json: RawUTF8): boolean; tmp: TSynTempBuffer; // private copy begin tmp.Init(json); - result := (DynArrayLoadJSON(values,tmp.buf,TypeInfo(TRawUTF8DynArray))<>nil) and - LoadFromArray(values); - tmp.Done; + try + result := (DynArrayLoadJSON(values,tmp.buf,TypeInfo(TRawUTF8DynArray))<>nil) and + LoadFromArray(values); + finally + tmp.Done; + end; end; function TECCCertificateChain.LoadFromArray(const values: TRawUTF8DynArray): boolean; @@ -5318,7 +5328,7 @@ procedure TECDHEProtocolClient.ComputeHandshake(out aClient: TECDHEFrameClient); begin if fAES[false]<>nil then raise EECCException.CreateUTF8('%.ComputeHandshake already called',[self]); - FillCharFast(aClient,sizeof(aClient),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(aClient,sizeof(aClient),0); aClient.algo := fAlgo; TAESPRNG.Main.FillRandom(fRndA); aClient.RndA := fRndA; @@ -5417,7 +5427,7 @@ function TECDHEProtocolServer.ComputeHandshake(const aClient: TECDHEFrameClient; if fAlgo.auth<>authServer then if not Verify(@aClient,sizeof(aClient),aClient.QCA,result) then exit; - FillCharFast(aServer,sizeof(aServer),0); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(aServer,sizeof(aServer),0); aServer.algo := fAlgo; aServer.RndA := fRndA; TAESPRNG.Main.FillRandom(fRndB); diff --git a/ThirdParty/mORMot/Source/SynEcc32asm.inc b/ThirdParty/mORMot/Source/SynEcc32asm.inc index d7536eb8..b86e347b 100644 --- a/ThirdParty/mORMot/Source/SynEcc32asm.inc +++ b/ThirdParty/mORMot/Source/SynEcc32asm.inc @@ -6,7 +6,7 @@ This file is part of Synopse framework. - Synopse framework. Copyright (C) 2018 Arnaud Bouchez + Synopse framework. Copyright (C) 2019 Arnaud Bouchez Synopse Informatique - https://synopse.info Using secp256r1 curve from "simple and secure ECDH and ECDSA library" diff --git a/ThirdParty/mORMot/Source/SynLZ.pas b/ThirdParty/mORMot/Source/SynLZ.pas index b661fabb..ffba737c 100644 --- a/ThirdParty/mORMot/Source/SynLZ.pas +++ b/ThirdParty/mORMot/Source/SynLZ.pas @@ -5,7 +5,7 @@ { This file is part of Synopse SynLZ Compression. - Synopse SynLZ Compression. Copyright (C) 2018 Arnaud Bouchez + Synopse SynLZ Compression. Copyright (C) 2019 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2018 + Portions created by the Initial Developer are Copyright (C) 2019 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/ThirdParty/mORMot/Source/SynMustache.pas b/ThirdParty/mORMot/Source/SynMustache.pas new file mode 100644 index 00000000..05cbddad --- /dev/null +++ b/ThirdParty/mORMot/Source/SynMustache.pas @@ -0,0 +1,1441 @@ +/// Logic-less {{mustache}} template rendering +// - this unit is a part of the freeware Synopse mORMot framework, +// licensed under a MPL/GPL/LGPL tri-license; version 1.18 +unit SynMustache; + +{ + This file is part of Synopse mORMot framework. + + Synopse mORMot framework. Copyright (C) 2019 Arnaud Bouchez + Synopse Informatique - https://synopse.info + + *** BEGIN LICENSE BLOCK ***** + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is Synopse mORMot framework. + + The Initial Developer of the Original Code is Arnaud Bouchez. + + Portions created by the Initial Developer are Copyright (C) 2019 + the Initial Developer. All Rights Reserved. + + Contributor(s): + - shura1990 + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + + ***** END LICENSE BLOCK ***** + + + Version 1.18 + - initial revision + +} + + +{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER + +interface + +uses + {$ifdef HASINLINENOTX86} + {$ifdef MSWINDOWS}Windows,{$endif} // for Lock/UnLock inlining + {$endif} + Variants, + SysUtils, + SynCommons; + + +type + /// exception raised during process of a {{mustache}} template + ESynMustache = class(ESynException); + + /// identify the {{mustache}} tag kind + // - mtVariable if the tag is a variable - e.g. {{myValue}} - or an Expression + // Helper - e.g. {{helperName valueName}} + // - mtVariableUnescaped to unescape the variable HTML - e.g. + // {{{myRawValue}}} or {{& name}} + // - mtSection and mtInvertedSection for sections beginning - e.g. + // {{#person}} or {{^person}} + // - mtSectionEnd for sections ending - e.g. {{/person}} + // - mtComment for comments - e.g. {{! ignore me}} + // - mtPartial for partials - e.g. {{> next_more}} + // - mtSetPartial for setting an internal partial - e.g. + // {{=}} - + // Warning: current implementation only supports two character delimiters + // - mtTranslate for content i18n via a callback - e.g. {{"English text}} + // - mtText for all text that appears outside a symbol + TSynMustacheTagKind = ( + mtVariable, mtVariableUnescape, + mtSection, mtInvertedSection, mtSectionEnd, + mtComment, mtPartial, mtSetPartial, mtSetDelimiter, mtTranslate, mtText); + + /// store a {{mustache}} tag + TSynMustacheTag = record + /// the kind of the tag + Kind: TSynMustacheTagKind; + /// points to the mtText buffer start + // - main template's text is not allocated as a separate string during + // parsing, but will rather be copied directly from the template memory + TextStart: PUTF8Char; + /// stores the mtText buffer length + TextLen: integer; + /// the index in Tags[] of the other end of this section + // - either the index of mtSectionEnd for mtSection/mtInvertedSection + // - or the index of mtSection/mtInvertedSection for mtSectionEnd + SectionOppositeIndex: integer; + /// the tag content, excluding trailing {{ }} and corresponding symbol + // - is not set for mtText nor mtSetDelimiter + Value: RawUTF8; + end; + + /// store all {{mustache}} tags of a given template + TSynMustacheTagDynArray = array of TSynMustacheTag; + + /// states the section content according to a given value + // - msNothing for false values or empty lists + // - msSingle for non-false values but not a list + // - msList for non-empty lists + TSynMustacheSectionType = (msNothing,msSingle,msSinglePseudo,msList); + + TSynMustache = class; + + /// callback signature used to process an Expression Helper variable + // - i.e. {{helperName value}} tags + // - returned value will be used to process as replacement of a single {{tag}} + TSynMustacheHelperEvent = procedure(const Value: variant; out result: variant) of object; + + /// used to store a registered Expression Helper implementation + TSynMustacheHelper = record + /// the Expression Helper name + Name: RawUTF8; + /// the corresponding callback to process the tag + Event: TSynMustacheHelperEvent; + end; + + /// used to store all registered Expression Helpers + // - i.e. {{helperName value}} tags + // - use TSynMustache.HelperAdd/HelperDelete class methods to manage the list + // or retrieve standard helpers via TSynMustache.HelpersGetStandardList + TSynMustacheHelpers = array of TSynMustacheHelper; + + /// handle {{mustache}} template rendering context, i.e. all values + // - this abstract class should not be used directly, but rather any + // other overridden class + TSynMustacheContext = class + protected + fContextCount: integer; + fWriter: TTextWriter; + fOwner: TSynMustache; + fEscapeInvert: boolean; + fHelpers: TSynMustacheHelpers; + fOnStringTranslate: TOnStringTranslate; + procedure TranslateBlock(Text: PUTF8Char; TextLen: Integer); virtual; + procedure PopContext; virtual; abstract; + procedure AppendValue(const ValueName: RawUTF8; UnEscape: boolean); + virtual; abstract; + function AppendSection(const ValueName: RawUTF8): TSynMustacheSectionType; + virtual; abstract; + function GotoNextListItem: boolean; + virtual; abstract; + public + /// initialize the rendering context for the given text writer + constructor Create(Owner: TSynMustache; WR: TTextWriter); + /// the registered Expression Helpers, to handle {{helperName value}} tags + // - use TSynMustache.HelperAdd/HelperDelete class methods to manage the list + // or retrieve standard helpers via TSynMustache.HelpersGetStandardList + property Helpers: TSynMustacheHelpers read fHelpers write fHelpers; + /// access to the {{"English text}} translation callback + property OnStringTranslate: TOnStringTranslate + read fOnStringTranslate write fOnStringTranslate; + /// read-only access to the associated text writer instance + property Writer: TTextWriter read fWriter; + /// invert the HTML characters escaping process + // - by default, {{value}} will escape value chars, and {{{value}} won't + // - set this property to true to force {{value}} NOT to escape HTML chars + // and {{{value}} escaping chars (may be useful e.g. for code generation) + property EscapeInvert: boolean read fEscapeInvert write fEscapeInvert; + end; + + /// handle {{mustache}} template rendering context from a custom variant + // - the context is given via a custom variant type implementing + // TSynInvokeableVariantType.Lookup, e.g. TDocVariant or TSMVariant + TSynMustacheContextVariant = class(TSynMustacheContext) + protected + fContext: array of record + Document: TVarData; + DocumentType: TSynInvokeableVariantType; + ListCount: integer; + ListCurrent: integer; + ListCurrentDocument: TVarData; + ListCurrentDocumentType: TSynInvokeableVariantType; + end; + fTempGetValueFromContextHelper: TVariantDynArray; + procedure PushContext(aDoc: TVarData); + procedure PopContext; override; + procedure AppendValue(const ValueName: RawUTF8; UnEscape: boolean); override; + function AppendSection(const ValueName: RawUTF8): TSynMustacheSectionType; override; + function GotoNextListItem: boolean; override; + function GetDocumentType(const aDoc: TVarData): TSynInvokeableVariantType; + function GetValueFromContext(const ValueName: RawUTF8; var Value: TVarData): TSynMustacheSectionType; + function GetValueCopyFromContext(const ValueName: RawUTF8): variant; + procedure AppendVariant(const Value: variant; UnEscape: boolean); + public + /// initialize the context from a custom variant document + // - note that the aDocument instance shall be available during all + // lifetime of this TSynMustacheContextVariant instance + // - you should not use this constructor directly, but the + // corresponding TSynMustache.Render*() methods + constructor Create(Owner: TSynMustache; WR: TTextWriter; SectionMaxCount: integer; + const aDocument: variant); + end; + + /// maintain a list of {{mustache}} partials + // - this list of partials template could be supplied to TSynMustache.Render() + // method, to render {{>partials}} as expected + // - using a dedicated class allows to share the partials between execution + // context, without recurring to non SOLID global variables + // - you may also define "internal" partials, e.g. {{partialName}} template + procedure Add(const aName,aTemplate: RawUTF8); overload; + /// register a {{>partialName}} template + procedure Add(const aName: RawUTF8; aTemplateStart,aTemplateEnd: PUTF8Char); overload; + /// delete the partials + destructor Destroy; override; + end; + + /// stores one {{mustache}} pre-rendered template + // - once parsed, a template will be stored in this class instance, to be + // rendered lated via the Render() method + // - you can use the Parse() class function to maintain a shared cache of + // parsed templates + // - implements all official mustache specifications, and some extensions + // - handles {{.}} pseudo-variable for the current context object (very + // handy when looping through a simple list, for instance) + // - handles {{-index}} pseudo-variable for the current context array index + // (1-based value) so that e.g. + // "My favorite things:\n{{#things}}{{-index}}. {{.}}\n{{/things}}" + // over {things:["Peanut butter", "Pen spinning", "Handstands"]} renders as + // "My favorite things:\n1. Peanut butter\n2. Pen spinning\n3. Handstands\n" + // - you could use {{-index0}} for 0-based index value + // - handles -first -last and -odd pseudo-section keys, e.g. + // "{{#things}}{{^-first}}, {{/-first}}{{.}}{{/things}}" + // over {things:["one", "two", "three"]} renders as 'one, two, three' + // - allows inlined partial templates , to be defined e.g. as + // {{ <= >= <> operators over two values: + // $ {{#if .,"=",123}} {{#if Total,">",1000}} {{#if info,"<>",""}} + // which may be shortened as such: + // $ {{#if .=123}} {{#if Total>1000}} {{#if info<>""}} + class function HelpersGetStandardList: TSynMustacheHelpers; overload; + /// returns a list of most used static Expression Helpers, adding some + // custom callbacks + // - is just a wrapper around HelpersGetStandardList and HelperAdd() + class function HelpersGetStandardList(const aNames: array of RawUTF8; + const aEvents: array of TSynMustacheHelperEvent): TSynMustacheHelpers; overload; + + /// renders the {{mustache}} template into a destination text buffer + // - the context is given via our abstract TSynMustacheContext wrapper + // - the rendering extended in fTags[] is supplied as parameters + // - you can specify a list of partials via TSynMustachePartials.CreateOwned + procedure RenderContext(Context: TSynMustacheContext; TagStart,TagEnd: integer; + Partials: TSynMustachePartials; NeverFreePartials: boolean); + /// renders the {{mustache}} template from a variant defined context + // - the context is given via a custom variant type implementing + // TSynInvokeableVariantType.Lookup, e.g. TDocVariant or TSMVariant + // - you can specify a list of partials via TSynMustachePartials.CreateOwned, + // a list of Expression Helpers, or a custom {{"English text}} callback + // - can be used e.g. via a TDocVariant: + // !var mustache := TSynMustache; + // ! doc: variant; + // ! html: RawUTF8; + // !begin + // ! mustache := TSynMustache.Parse( + // ! 'Hello {{name}}'#13#10'You have just won {{value}} dollars!'); + // ! TDocVariant.New(doc); + // ! doc.name := 'Chris'; + // ! doc.value := 10000; + // ! html := mustache.Render(doc); + // ! // here html='Hello Chris'#13#10'You have just won 10000 dollars!' + // - you can also retrieve the context from an ORM query of mORMot.pas: + // ! dummy := TSynMustache.Parse( + // ! '{{#items}}'#13#10'{{Int}}={{Test}}'#13#10'{{/items}}').Render( + // ! aClient.RetrieveDocVariantArray(TSQLRecordTest,'items','Int,Test')); + // - set EscapeInvert = true to force {{value}} NOT to escape HTML chars + // and {{{value}} escaping chars (may be useful e.g. for code generation) + function Render(const Context: variant; Partials: TSynMustachePartials=nil; + Helpers: TSynMustacheHelpers=nil; OnTranslate: TOnStringTranslate=nil; + EscapeInvert: boolean=false): RawUTF8; + /// renders the {{mustache}} template from JSON defined context + // - the context is given via a JSON object, defined from UTF-8 buffer + // - you can specify a list of partials via TSynMustachePartials.CreateOwned, + // a list of Expression Helpers, or a custom {{"English text}} callback + // - is just a wrapper around Render(_JsonFast()) + // - you can write e.g. with the extended JSON syntax: + // ! html := mustache.RenderJSON('{things:["one", "two", "three"]}'); + // - set EscapeInvert = true to force {{value}} NOT to escape HTML chars + // and {{{value}} escaping chars (may be useful e.g. for code generation) + function RenderJSON(const JSON: RawUTF8; Partials: TSynMustachePartials=nil; + Helpers: TSynMustacheHelpers=nil; OnTranslate: TOnStringTranslate=nil; + EscapeInvert: boolean=false): RawUTF8; overload; + /// renders the {{mustache}} template from JSON defined context + // - the context is given via a JSON object, defined with parameters + // - you can specify a list of partials via TSynMustachePartials.CreateOwned, + // a list of Expression Helpers, or a custom {{"English text}} callback + // - is just a wrapper around Render(_JsonFastFmt()) + // - you can write e.g. with the extended JSON syntax: + // ! html := mustache.RenderJSON('{name:?,value:?}',[],['Chris',10000]); + // - set EscapeInvert = true to force {{value}} NOT to escape HTML chars + // and {{{value}} escaping chars (may be useful e.g. for code generation) + function RenderJSON(const JSON: RawUTF8; const Args,Params: array of const; + Partials: TSynMustachePartials=nil; Helpers: TSynMustacheHelpers=nil; + OnTranslate: TOnStringTranslate=nil; + EscapeInvert: boolean=false): RawUTF8; overload; + + /// read-only access to the raw {{mustache}} template content + property Template: RawUTF8 read fTemplate; + /// the maximum possible number of nested contexts + property SectionMaxCount: Integer read fSectionMaxCount; + end; + + +const + /// this constant can be used to define as JSON a tag value + NULL_OR_TRUE: array[boolean] of RawUTF8 = ('null','true'); + + /// this constant can be used to define as JSON a tag value as separator + NULL_OR_COMMA: array[boolean] of RawUTF8 = ('null','","'); + + +implementation + +function KindToText(Kind: TSynMustacheTagKind): PShortString; +begin + result := GetEnumName(TypeInfo(TSynMustacheTagKind),ord(Kind)); +end; + +type + TSynMustacheParser = class + protected + fTagStart, fTagStop: word; + fPos, fPosMin, fPosMax, fPosTagStart: PUTF8Char; + fTagCount: integer; + fTemplate: TSynMustache; + fScanStart, fScanEnd: PUTF8Char; + function Scan(ExpectedTag: Word): boolean; + procedure AddTag(aKind: TSynMustacheTagKind; + aStart: PUTF8Char=nil; aEnd: PUTF8Char=nil); + public + constructor Create(Template: TSynMustache; const DelimiterStart, DelimiterStop: RawUTF8); + procedure Parse(P,PEnd: PUTF8Char); + end; + + TSynMustacheCache = class(TRawUTF8ListHashedLocked) + public + function Parse(const aTemplate: RawUTF8): TSynMustache; + function UnParse(const aTemplate: RawUTF8): boolean; + end; + +var + SynMustacheCache: TSynMustacheCache = nil; + + + +{ TSynMustacheParser } + +procedure TSynMustacheParser.AddTag(aKind: TSynMustacheTagKind; + aStart, aEnd: PUTF8Char); +begin + if (aStart=nil) or (aEnd=nil) then begin + aStart := fScanStart; + aEnd := fScanEnd; + case aKind of + mtComment, mtSection, mtSectionEnd, mtInvertedSection, mtSetDelimiter, mtPartial: begin + // (indented) standalone lines should be removed from the template + if aKind<>mtPartial then + while (fPosTagStart>fPosMin) and (fPosTagStart[-1] in [' ',#9]) do + dec(fPosTagStart); // ignore any indentation chars + if (fPosTagStart=fPosMin) or (fPosTagStart[-1]=#$0A) then + // tag starts on a new line -> check if ends on the same line + if (fPos>fPosMax) or (fPos^=#$0A) or (PWord(fPos)^=$0A0D) then begin + if fPos<=fPosMax then + if fPos^=#$0A then + inc(fPos) else + if PWord(fPos)^=$0A0D then + inc(fPos,2); + if fTagCount>0 then // remove any indentation chars from previous text + with fTemplate.fTags[fTagCount-1] do + if Kind=mtText then + while (TextLen>0) and (TextStart[TextLen-1] in [' ',#9]) do + dec(TextLen); + end; + end; + end; + end; + if aEnd<=aStart then + exit; + if fTagCount>=length(fTemplate.fTags) then + SetLength(fTemplate.fTags,NextGrow(fTagCount)); + with fTemplate.fTags[fTagCount] do begin + Kind := aKind; + SectionOppositeIndex := -1; + case aKind of + mtText, mtComment, mtTranslate: begin + TextStart := aStart; + TextLen := aEnd-aStart; + end; + else begin + TextStart := fPosTagStart; + TextLen := aEnd-fPosTagStart; + // superfluous in-tag whitespace should be ignored + while (aStartaStart) and (aEnd[-1]<=' ') do dec(aEnd); + if aEnd=aStart then + raise ESynMustache.CreateFmt('Void %s identifier',[KindToText(aKind)^]); + SetString(Value,PAnsiChar(aStart),aEnd-aStart); + end; + end; + end; + inc(fTagCount); +end; + +constructor TSynMustacheParser.Create(Template: TSynMustache; + const DelimiterStart, DelimiterStop: RawUTF8); +begin + fTemplate := Template; + if length(DelimiterStart)<>2 then + raise ESynMustache.CreateFmt('DelimiterStart="%s"',[DelimiterStart]); + if length(DelimiterStop)<>2 then + raise ESynMustache.CreateFmt('DelimiterStop="%s"',[DelimiterStop]); + fTagStart := PWord(DelimiterStart)^; + fTagStop := PWord(DelimiterStop)^; +end; + +function GotoNextTag(P,PMax: PUTF8Char; ExpectedTag: Word): PUTF8Char; +begin + if PExpectedTag then begin + inc(P); + if P0) and IdemPropNameU(finish,pointer(start),i-1); + end; +end; + +procedure TSynMustacheParser.Parse(P, PEnd: PUTF8Char); +var Kind: TSynMustacheTagKind; + Symbol: AnsiChar; + i,j,secCount,secLevel: integer; +begin + secCount := 0; + if P=nil then + exit; + fPos := P; + fPosMin := P; + fPosMax := PEnd-1; + repeat + if not Scan(fTagStart) then + break; + fPosTagStart := fScanEnd; + AddTag(mtText); + if fPos>=fPosMax then + break; + Symbol := fPos^; + case Symbol of + '=': Kind := mtSetDelimiter; + '{', + '&': Kind := mtVariableUnescape; + '#': Kind := mtSection; + '^': Kind := mtInvertedSection; + '/': Kind := mtSectionEnd; + '!': Kind := mtComment; + '>': Kind := mtPartial; + '<': Kind := mtSetPartial; + '"': Kind := mtTranslate; + else Kind := mtVariable; + end; + if Kind<>mtVariable then + inc(fPos); + if not Scan(fTagStop) then + raise ESynMustache.CreateFmt('Unfinished {{tag "%s"',[fPos]); + case Kind of + mtSetDelimiter: begin + if (fScanEnd-fScanStart<>6) or (fScanEnd[-1]<>'=') then + raise ESynMustache.Create('mtSetDelimiter syntax is e.g. {{=<% %>=}}'); + fTagStart := PWord(fScanStart)^; + fTagStop := PWord(fScanStart+3)^; + continue; // do not call AddTag(mtSetDelimiter) + end; + mtVariableUnescape: + if (Symbol='{') and (fTagStop=32125) and (PWord(fPos-1)^=32125) then + inc(fPos); // {{{name}}} -> point after }}} + end; + AddTag(Kind); + until false; + AddTag(mtText,fPos,fPosMax+1); + for i := 0 to fTagCount-1 do + with fTemplate.fTags[i] do + case Kind of + mtSection, mtInvertedSection, mtSetPartial: begin + inc(secCount); + if secCount>fTemplate.fSectionMaxCount then + fTemplate.fSectionMaxCount := secCount; + secLevel := 1; + for j := i+1 to fTagCount-1 do + case fTemplate.fTags[j].Kind of + mtSection, mtInvertedSection, mtSetPartial: + inc(secLevel); + mtSectionEnd: begin + dec(secLevel); + if secLevel=0 then + if SectionNameMatch(Value,fTemplate.fTags[j].Value) then begin + fTemplate.fTags[j].SectionOppositeIndex := i; + SectionOppositeIndex := j; + if Kind=mtSetPartial then begin + if fTemplate.fInternalPartials=nil then + fTemplate.fInternalPartials := TSynMustachePartials.Create; + fTemplate.fInternalPartials.Add(Value, + TextStart+TextLen+2,fTemplate.fTags[j].TextStart); + end; + break; + end else + raise ESynMustache.CreateFmt('Got {{/%s}}, expected {{/%s}}', + [Value,fTemplate.fTags[j].Value]); + end; + end; + if SectionOppositeIndex<0 then + raise ESynMustache.CreateFmt('Missing section end {{/%s}}',[Value]); + end; + mtSectionEnd: begin + dec(secCount); + if SectionOppositeIndex<0 then + raise ESynMustache.CreateFmt('Unexpected section end {{/%s}}',[Value]); + end; + end; + SetLength(fTemplate.fTags,fTagCount); +end; + + +{ TSynMustacheCache } + +function TSynMustacheCache.Parse(const aTemplate: RawUTF8): TSynMustache; +var i: integer; +begin + fSafe.Lock; + try + i := IndexOf(aTemplate); // fast instance retrieval from shared cache + if i>=0 then begin + result := TSynMustache(Objects[i]); + exit; + end; + result := TSynMustache.Create(aTemplate); + AddObject(aTemplate,result); + finally + fSafe.UnLock; + end; +end; + +function TSynMustacheCache.UnParse(const aTemplate: RawUTF8): boolean; +var i: integer; +begin + result := false; + if self=nil then + exit; + fSafe.Lock; + try + i := IndexOf(aTemplate); + if i>=0 then begin + Delete(i); + result := true; + end; + finally + fSafe.UnLock; + end; +end; + + +{ TSynMustache } + +class function TSynMustache.Parse(const aTemplate: RawUTF8): TSynMustache; +begin + if SynMustacheCache=nil then + GarbageCollectorFreeAndNil(SynMustacheCache,TSynMustacheCache.Create(true)); + result := SynMustacheCache.Parse(aTemplate); +end; + +class function TSynMustache.UnParse(const aTemplate: RawUTF8): boolean; +begin + result := SynMustacheCache.UnParse(aTemplate); +end; + +class function TSynMustache.TryRenderJson(const aTemplate, aJSON: RawUTF8; + out aContent: RawUTF8): boolean; +var mus: TSynMustache; +begin + if aTemplate<>'' then + try + mus := Parse(aTemplate); + aContent := mus.RenderJSON(aJSON); + result := true; + except + result := false; + end else + result := false; +end; + +constructor TSynMustache.Create(const aTemplate: RawUTF8); +begin + Create(pointer(aTemplate),length(aTemplate)); +end; + +constructor TSynMustache.Create(aTemplate: PUTF8Char; aTemplateLen: integer); +begin + inherited Create; + fTemplate := aTemplate; + with TSynMustacheParser.Create(self,'{{','}}') do + try + Parse(aTemplate,aTemplate+aTemplateLen); + finally + Free; + end; +end; + +type + TSynMustacheProcessSection = procedure of object; + +procedure TSynMustache.RenderContext(Context: TSynMustacheContext; + TagStart,TagEnd: integer; Partials: TSynMustachePartials; NeverFreePartials: boolean); +var partial: TSynMustache; +begin + try + while TagStart<=TagEnd do begin + with fTags[TagStart] do + case Kind of + mtText: + if TextLen<>0 then // may be 0 e.g. for standalone without previous Line + Context.fWriter.AddNoJSONEscape(TextStart,TextLen); + mtVariable: + Context.AppendValue(Value,false); + mtVariableUnescape: + Context.AppendValue(Value,true); + mtSection: + case Context.AppendSection(Value) of + msNothing: begin // e.g. for no key, false value, or empty list + TagStart := SectionOppositeIndex; + continue; // ignore whole section + end; + msList: begin + while Context.GotoNextListItem do + RenderContext(Context,TagStart+1,SectionOppositeIndex-1,Partials,true); + TagStart := SectionOppositeIndex; + continue; // ignore whole section since we just rendered it as a list + end; + // msSingle,msSinglePseudo: process the section once with current context + end; + mtInvertedSection: // display section for no key, false value, or empty list + if Context.AppendSection(Value)<>msNothing then begin + TagStart := SectionOppositeIndex; + continue; // ignore whole section + end; + mtSectionEnd: + if (fTags[SectionOppositeIndex].Kind in [mtSection,mtInvertedSection]) and + (Value[1]<>'-') and (PosEx(' ',fTags[SectionOppositeIndex].Value)=0) then + Context.PopContext; + mtComment: + ; // just ignored + mtPartial: begin + partial := fInternalPartials.GetPartial(Value); + if (partial=nil) and (Context.fOwner<>self) then // recursive call + partial := Context.fOwner.fInternalPartials.GetPartial(Value); + if (partial=nil) and (Partials<>nil) then + partial := Partials.GetPartial(Value); + if partial<>nil then + partial.RenderContext(Context,0,high(partial.fTags),Partials,true); + end; + mtSetPartial: + TagStart := SectionOppositeIndex; // ignore whole internal {{0 then + Context.TranslateBlock(TextStart,TextLen); + else + raise ESynMustache.CreateFmt('Kind=%s not implemented yet', + [KindToText(fTags[TagStart].Kind)^]); + end; + inc(TagStart); + end; + finally + if (Partials<>nil) and (Partials.fOwned) and not NeverFreePartials then + Partials.Free; + end; +end; + +function TSynMustache.Render(const Context: variant; + Partials: TSynMustachePartials; Helpers: TSynMustacheHelpers; + OnTranslate: TOnStringTranslate; EscapeInvert: boolean): RawUTF8; +var W: TTextWriter; + Ctxt: TSynMustacheContext; +begin + W := TTextWriter.CreateOwnedStream(4096); + try + Ctxt := TSynMustacheContextVariant.Create(self,W,SectionMaxCount,Context); + try + Ctxt.Helpers := Helpers; + Ctxt.OnStringTranslate := OnTranslate; + Ctxt.EscapeInvert := EscapeInvert; + RenderContext(Ctxt,0,high(fTags),Partials,false); + W.SetText(result); + finally + Ctxt.Free; + end; + finally + W.Free; + end; +end; + +function TSynMustache.RenderJSON(const JSON: RawUTF8; + Partials: TSynMustachePartials; Helpers: TSynMustacheHelpers; + OnTranslate: TOnStringTranslate; EscapeInvert: boolean): RawUTF8; +var context: variant; +begin + _Json(JSON,context,JSON_OPTIONS[true]); + result := Render(context,Partials,Helpers,OnTranslate,EscapeInvert); +end; + +function TSynMustache.RenderJSON(const JSON: RawUTF8; const Args, + Params: array of const; Partials: TSynMustachePartials; + Helpers: TSynMustacheHelpers; OnTranslate: TOnStringTranslate; + EscapeInvert: boolean): RawUTF8; +var context: variant; +begin + _Json(FormatUTF8(JSON,Args,Params,true),context,JSON_OPTIONS[true]); + result := Render(context,Partials,Helpers,OnTranslate,EscapeInvert); +end; + +destructor TSynMustache.Destroy; +begin + FreeAndNil(fInternalPartials); + inherited; +end; + +class procedure TSynMustache.HelperAdd(var Helpers: TSynMustacheHelpers; + const aName: RawUTF8; aEvent: TSynMustacheHelperEvent); +var n,i: integer; +begin + n := length(Helpers); + for i := 0 to n-1 do + if IdemPropNameU(Helpers[i].Name,aName) then begin + Helpers[i].Event := aEvent; + exit; + end; + SetLength(Helpers,n+1); + Helpers[n].Name := aName; + Helpers[n].Event := aEvent; +end; + +class procedure TSynMustache.HelperAdd(var Helpers: TSynMustacheHelpers; + const aNames: array of RawUTF8; const aEvents: array of TSynMustacheHelperEvent); +var n,count,i: integer; +begin + n := length(aNames); + if n<>length(aEvents) then + exit; + count := length(Helpers); + SetLength(Helpers,count+n); + for i := 0 to n-1 do + with Helpers[count+i] do begin + Name := aNames[i]; + Event := aEvents[i]; + end; +end; + +class procedure TSynMustache.HelperDelete(var Helpers: TSynMustacheHelpers; + const aName: RawUTF8); +var n,i,j: integer; +begin + n := length(Helpers); + for i := 0 to n-1 do + if IdemPropNameU(Helpers[i].Name,aName) then begin + for j := i to n-2 do + Helpers[j] := Helpers[j+1]; + SetLength(Helpers,n-1); + exit; + end; +end; + +class function TSynMustache.HelperFind(const Helpers: TSynMustacheHelpers; + aName: PUTF8Char; aNameLen: integer): integer; +begin + for result := 0 to length(Helpers)-1 do + if IdemPropNameU(Helpers[result].Name,aName,aNameLen) then + exit; + result := -1; +end; + +var + HelpersStandardList: TSynMustacheHelpers; + +class function TSynMustache.HelpersGetStandardList: TSynMustacheHelpers; +begin + if HelpersStandardList=nil then + HelperAdd(HelpersStandardList, + ['DateTimeToText','DateToText','DateFmt','TimeLogToText','JSONQuote','JSONQuoteURI', + 'ToJSON','WikiToHtml','BlobToBase64','EnumTrim','EnumTrimRight','PowerOfTwo', + 'Equals','If','NewGUID','ExtractFileName','Lower','Upper'], + [DateTimeToText,DateToText,DateFmt,TimeLogToText,JSONQuote,JSONQuoteURI, + ToJSON,WikiToHtml,BlobToBase64,EnumTrim,EnumTrimRight,PowerOfTwo, + Equals_,If_,NewGUID,ExtractFileName,Lower,Upper]); + result := HelpersStandardList; +end; + +class function TSynMustache.HelpersGetStandardList(const aNames: array of RawUTF8; + const aEvents: array of TSynMustacheHelperEvent): TSynMustacheHelpers; +begin + result := HelpersGetStandardList; + HelperAdd(result,aNames,aEvents); +end; + +class procedure TSynMustache.DateTimeToText(const Value: variant; out result: variant); +var Time: TTimeLogBits; + dt: TDateTime; +begin + if VariantToDateTime(Value,dt) then begin + Time.From(dt,false); + result := Time.i18nText; + end else + SetVariantNull(result); +end; + +class procedure TSynMustache.DateToText(const Value: variant; out result: variant); +var Time: TTimeLogBits; + dt: TDateTime; +begin + if VariantToDateTime(Value,dt) then begin + Time.From(dt,true); + result := Time.i18nText; + end else + SetVariantNull(result); +end; + +class procedure TSynMustache.DateFmt(const Value: variant; out result: variant); +var dt: TDateTime; +begin // {{DateFmt DateValue,"dd/mm/yyy"}} + with _Safe(Value)^ do + if (Kind=dvArray) and (Count=2) and VariantToDateTime(Values[0],dt) then + result := FormatDateTime(Values[1],dt) else + SetVariantNull(result); +end; + +class procedure TSynMustache.TimeLogToText(const Value: variant; out result: variant); +var Time: TTimeLogBits; +begin + if VariantToInt64(Value,Time.Value) then + result := Time.i18nText else + SetVariantNull(result); +end; + +class procedure TSynMustache.ToJSON(const Value: variant; out result: variant); +begin + RawUTF8ToVariant(JSONReformat(VariantToUTF8(Value)),result); +end; + +class procedure TSynMustache.JSONQuote(const Value: variant; out result: variant); +var json: RawUTF8; +begin + QuotedStrJSON(VariantToUTF8(Value),json); + RawUTF8ToVariant(json,result); +end; + +class procedure TSynMustache.JSONQuoteURI(const Value: variant; out result: variant); +var json: RawUTF8; +begin + QuotedStrJSON(VariantToUTF8(Value),json); + RawUTF8ToVariant(UrlEncode(json),result); +end; + +class procedure TSynMustache.WikiToHtml(const Value: variant; out result: variant); +var txt: RawUTF8; +begin + txt := VariantToUTF8(Value); + if txt<>'' then + with TTextWriter.CreateOwnedStream do + try + AddHtmlEscapeWiki(pointer(txt)); + SetText(txt); + finally + Free; + end; + RawUTF8ToVariant(txt,result); +end; + +class procedure TSynMustache.BlobToBase64(const Value: variant; out result: variant); +var tmp: RawUTF8; + wasString: boolean; +begin + VariantToUTF8(Value,tmp,wasString); + if wasString and (pointer(tmp)<>nil) then begin + if PInteger(tmp)^ and $00ffffff=JSON_BASE64_MAGIC then + delete(tmp,1,3); + RawUTF8ToVariant(tmp,result); + end else + result := Value; +end; + +class procedure TSynMustache.EnumTrim(const Value: variant; out result: variant); +var tmp: RawUTF8; + wasString: boolean; + short: PUTF8Char; +begin + VariantToUTF8(Value,tmp,wasString); + short := TrimLeftLowerCase(tmp); + RawUTF8ToVariant(short,StrLen(short),result); +end; + +class procedure TSynMustache.EnumTrimRight(const Value: variant; out result: variant); +var tmp: RawUTF8; + wasString: boolean; + i,L: integer; +begin + VariantToUTF8(Value,tmp,wasString); + L := length(tmp); + for i := 1 to L do + if not (tmp[i] in ['a'..'z']) then begin + L := i-1; + break; + end; + RawUTF8ToVariant(Pointer(tmp),L,result); +end; + +class procedure TSynMustache.PowerOfTwo(const Value: variant; out result: variant); +var V: Int64; +begin + if TVarData(Value).VType>varNull then + if VariantToInt64(Value,V) then + result := Int64(1) shl V; +end; + +class procedure TSynMustache.Equals_(const Value: variant; out result: variant); +begin // {{#Equals .,12}} + with _Safe(Value)^ do + if (Kind=dvArray) and (Count=2) and + (SortDynArrayVariantComp(TVarData(Values[0]),TVarData(Values[1]),false)=0) then + result := true else + SetVariantNull(result); +end; + +class procedure TSynMustache.If_(const Value: variant; out result: variant); +var cmp: integer; + oper: RawUTF8; + wasString: boolean; +begin // {{#if .<>""}} or {{#if .,"=",123}} + SetVariantNull(result); + with _Safe(Value)^ do + if (Kind=dvArray) and (Count=3) then begin + VariantToUTF8(Values[1],oper,wasString); + if wasString and (oper<>'') then begin + cmp := SortDynArrayVariantComp(TVarData(Values[0]),TVarData(Values[2]),false); + case PWord(oper)^ of + ord('='): if cmp=0 then result := True; + ord('>'): if cmp>0 then result := True; + ord('<'): if cmp<0 then result := True; + ord('>')+ord('=')shl 8: if cmp>=0 then result := True; + ord('<')+ord('=')shl 8: if cmp<=0 then result := True; + ord('<')+ord('>')shl 8: if cmp<>0 then result := True; + end; + end; + end; +end; + +class procedure TSynMustache.NewGUID(const Value: variant; out result: variant); +var g: TGUID; +begin + CreateGUID(g); + RawUTF8ToVariant(GUIDToRawUTF8(g),result); +end; + +class procedure TSynMustache.ExtractFileName(const Value: variant; out result: variant); +begin + result := SysUtils.ExtractFileName(Value); +end; + +class procedure TSynMustache.Lower(const Value: variant; out result: variant); +begin + result := SysUtils.LowerCase(Value); +end; + +class procedure TSynMustache.Upper(const Value: variant; out result: variant); +begin + result := SysUtils.UpperCase(Value); +end; + + +{ TSynMustacheContext } + +constructor TSynMustacheContext.Create(Owner: TSynMustache; WR: TTextWriter); +begin + fOwner := Owner; + fWriter := WR; +end; + +procedure TSynMustacheContext.TranslateBlock(Text: PUTF8Char; TextLen: Integer); +var s: string; +begin + if Assigned(OnStringTranslate) then begin + UTF8DecodeToString(Text,TextLen,s); + OnStringTranslate(s); + fWriter.AddNoJSONEscapeString(s); + end else + fWriter.AddNoJSONEscape(Text,TextLen); +end; + + +{ TSynMustacheContextVariant } + +constructor TSynMustacheContextVariant.Create(Owner: TSynMustache; WR: TTextWriter; + SectionMaxCount: integer; const aDocument: variant); +begin + inherited Create(Owner,WR); + SetLength(fContext,SectionMaxCount+1); + PushContext(TVarData(aDocument)); // weak copy +end; + +function TSynMustacheContextVariant.GetDocumentType( + const aDoc: TVarData): TSynInvokeableVariantType; +begin + result := nil; + if aDoc.VType<=varAny then + exit; + if (fContextCount>0) and (fContext[0].DocumentType<>nil) and + (aDoc.VType=fContext[0].DocumentType.VarType) then + result := fContext[0].DocumentType else + if not (FindCustomVariantType(aDoc.VType,TCustomVariantType(result)) and + result.InheritsFrom(TSynInvokeableVariantType)) then + result := nil; +end; + +procedure TSynMustacheContextVariant.PushContext(aDoc: TVarData); +begin + if fContextCount>=length(fContext) then + SetLength(fContext,fContextCount+32); // roughtly set by SectionMaxCount + with fContext[fContextCount] do begin + Document := aDoc; + DocumentType := GetDocumentType(aDoc); + ListCurrent := -1; + if DocumentType=nil then + ListCount := -1 else + ListCount := DocumentType.IterateCount(aDoc); + end; + inc(fContextCount); +end; + +procedure TSynMustacheContextVariant.PopContext; +begin + if fContextCount>1 then + dec(fContextCount); +end; + +function TSynMustacheContextVariant.GetValueCopyFromContext( + const ValueName: RawUTF8): variant; +var tmp: TVarData; +begin + if (ValueName='') or (ValueName[1] in ['0'..'9','"','{','[']) or + (ValueName='true') or (ValueName='false') or (ValueName='null') then + VariantLoadJSON(result,ValueName,@JSON_OPTIONS[true]) else begin + GetValueFromContext(ValueName,tmp); + SetVariantByValue(variant(tmp),result); // copy value + end; +end; + +function TSynMustacheContextVariant.GetValueFromContext( + const ValueName: RawUTF8; var Value: TVarData): TSynMustacheSectionType; +var i,space,helper: Integer; + + procedure ProcessHelper; + var valnam: RawUTF8; + val: TVarData; + valArr: TDocVariantData absolute val; + valFree: boolean; + names: TRawUTF8DynArray; + res: PVarData; + j,k,n: integer; + begin + valnam := Copy(ValueName,space+1,maxInt); + valFree := false; + if valnam<>'' then begin + if valnam='.' then + GetValueFromContext(valnam,val) else + if ((valnam<>'') and (valnam[1] in ['1'..'9','"','{','['])) or + (valnam='true') or (valnam='false') or (valnam='null') then begin + // {{helper 123}} or {{helper "constant"}} or {{helper [1,2,3]}} + val.VType := varEmpty; + VariantLoadJson(variant(val),pointer(valnam),nil,@JSON_OPTIONS[true]); + valFree := true; + end else begin + for j := 1 to length(valnam) do + case valnam[j] of + ' ': break; // allows {{helper1 helper2 value}} recursive calls + ',': begin // {{helper value,123,"constant"}} + CSVToRawUTF8DynArray(Pointer(valnam),names,',',true); // TODO: handle 123,"a,b,c" + valArr.InitFast; + for k := 0 to High(names) do + valArr.AddItem(GetValueCopyFromContext(names[k])); + valFree := true; + break; + end; + '<','>','=': begin // {{#if .=123}} -> {{#if .,"=",123}} + k := j+1; + if valnam[k] in ['=','>'] then + inc(k); + valArr.InitArray([GetValueCopyFromContext(Copy(valnam,1,j-1)), + Copy(valnam,j,k-j),GetValueCopyFromContext(Copy(valnam,k,maxInt))],JSON_OPTIONS[true]); + valFree := true; + break; + end; + end; + if not valFree then + GetValueFromContext(valnam,val); + end; + end; + n := fContextCount+4; + if length(fTempGetValueFromContextHelper)0 then + Value := ListCurrentDocument else + Value := Document; + exit; + end; + space := PosEx(' ',ValueName); + if space>1 then begin // {{helper value}} + helper := TSynMustache.HelperFind(Helpers,pointer(ValueName),space-1); + if helper>=0 then begin + ProcessHelper; + result := msSinglePseudo; + exit; + end; // if helper not found, will return the unprocessed value + end; + for i := fContextCount-1 downto 0 do // recursive search of {{value}} + with fContext[i] do + if DocumentType<>nil then + if ListCount<0 then begin // single item context + DocumentType.Lookup(Value,Document,pointer(ValueName)); + if Value.VType>=varNull then + exit; + end else + if IdemPChar(pointer(ValueName),'-INDEX') then begin // {{-index}} + Value.VType := varInteger; + if ValueName[7]='0' then + Value.VInteger := ListCurrent else + Value.VInteger := ListCurrent+1; + exit; + end else + if (ListCurrentnil) then begin + ListCurrentDocumentType.Lookup(Value,ListCurrentDocument,pointer(ValueName)); + if Value.VType>=varNull then + exit; + end; + if space=0 then begin + space := length(ValueName); // {{helper}} + helper := TSynMustache.HelperFind(Helpers,pointer(ValueName),space); + if helper>=0 then begin + ProcessHelper; + result := msSinglePseudo; + end; + end; +end; + +procedure TSynMustacheContextVariant.AppendValue(const ValueName: RawUTF8; + UnEscape: boolean); +var Value: TVarData; +begin + GetValueFromContext(ValueName,Value); + AppendVariant(variant(Value),UnEscape); +end; + +procedure TSynMustacheContextVariant.AppendVariant(const Value: variant; + UnEscape: boolean); +var ValueText: RawUTF8; + wasString: boolean; +begin + if TVarData(Value).VType>varNull then + if VarIsNumeric(Value) then // avoid RawUTF8 conversion for plain numbers + fWriter.AddVariant(Value,twNone) else begin + if fEscapeInvert then + UnEscape := not UnEscape; + VariantToUTF8(Value,ValueText,wasString); + if UnEscape then + fWriter.AddNoJSONEscape(pointer(ValueText),length(ValueText)) else + fWriter.AddHtmlEscape(pointer(ValueText)); + end; +end; + +function TSynMustacheContextVariant.AppendSection( + const ValueName: RawUTF8): TSynMustacheSectionType; +var Value: TVarData; +begin + result := msNothing; + if (fContextCount>0) and (ValueName[1]='-') then + with fContext[fContextCount-1] do + if ListCount>=0 then begin + if ((ValueName='-first') and (ListCurrent=0)) or + ((ValueName='-last') and (ListCurrent=ListCount-1)) or + ((ValueName='-odd') and (ListCurrent and 1=0)) then + result := msSinglePseudo; + exit; + end; + result := GetValueFromContext(ValueName,Value); + if result<>msNothing then begin + if (Value.VType<=varNull) or + ((Value.VType=varBoolean) and not Value.VBoolean) then + result := msNothing; + exit; + end; + PushContext(Value); + if (Value.VType<=varNull) or + ((Value.VType=varBoolean) and not Value.VBoolean) then + exit; // null or false value will not display the section + with fContext[fContextCount-1] do + if ListCount<0 then + result := msSingle else // single item + if ListCount=0 then // empty list will not display the section + exit else + result := msList; // non-empty list +end; + +function TSynMustacheContextVariant.GotoNextListItem: boolean; +begin + result := false; + if fContextCount>0 then + with fContext[fContextCount-1] do begin + ListCurrentDocument.VType := varEmpty; + ListCurrentDocumentType := nil; + inc(ListCurrent); + if ListCurrent>=ListCount then + exit; + DocumentType.Iterate(ListCurrentDocument,Document,ListCurrent); + ListCurrentDocumentType := GetDocumentType(ListCurrentDocument); + result := true; + end; +end; + + +{ TSynMustachePartials } + +procedure TSynMustachePartials.Add(const aName, aTemplate: RawUTF8); +begin + fList.AddObject(aName,TSynMustache.Parse(aTemplate)); +end; + +procedure TSynMustachePartials.Add(const aName: RawUTF8; + aTemplateStart, aTemplateEnd: PUTF8Char); +var aTemplate: RawUTF8; +begin + SetString(aTemplate,PAnsiChar(aTemplateStart),aTemplateEnd-aTemplateStart); + Add(aName,aTemplate); +end; + +constructor TSynMustachePartials.Create; +begin + fList := TRawUTF8ListHashed.Create(false); +end; + +constructor TSynMustachePartials.CreateOwned( + const NameTemplatePairs: array of RawUTF8); +var A: integer; +begin + Create; + fOwned := true; + for A := 0 to high(NameTemplatePairs) div 2 do + Add(NameTemplatePairs[A*2],NameTemplatePairs[A*2+1]); +end; + +class function TSynMustachePartials.CreateOwned( + const Partials: variant): TSynMustachePartials; +var p: integer; +begin + result := nil; + if DocVariantType.IsOfType(Partials) then + with TDocVariantData(partials) do + if (Kind=dvObject) and (Count>0) then begin + result := TSynMustachePartials.Create; + result.fOwned := true; + for p := 0 to Count-1 do + result.Add(Names[p],VariantToUTF8(Values[p])); + end; +end; + +destructor TSynMustachePartials.Destroy; +begin + FreeAndNil(fList); + inherited; +end; + +function TSynMustachePartials.GetPartial( + const PartialName: RawUTF8): TSynMustache; +var i: integer; +begin + if self=nil then begin + result := nil; + exit; + end; + i := fList.IndexOf(PartialName); + if i<0 then + result := nil else + result := TSynMustache(fList.Objects[i]); +end; + +end. diff --git a/ThirdParty/mORMot/Source/SynTable.pas b/ThirdParty/mORMot/Source/SynTable.pas new file mode 100644 index 00000000..b59b2602 --- /dev/null +++ b/ThirdParty/mORMot/Source/SynTable.pas @@ -0,0 +1,13273 @@ +/// implement TSynTable/TSynTableStatement and TSynFilter/TSynValidate process +// - licensed under a MPL/GPL/LGPL tri-license; version 1.18 +unit SynTable; + +(* + This file is part of Synopse framework. + + Synopse framework. Copyright (C) 2019 Arnaud Bouchez + Synopse Informatique - https://synopse.info + + *** BEGIN LICENSE BLOCK ***** + Version: MPL 1.1/GPL 2.0/LGPL 2.1 + + The contents of this file are subject to the Mozilla Public License Version + 1.1 (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL + + Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + for the specific language governing rights and limitations under the License. + + The Original Code is Synopse framework. + + The Initial Developer of the Original Code is Arnaud Bouchez. + + Portions created by the Initial Developer are Copyright (C) 2019 + the Initial Developer. All Rights Reserved. + + Contributor(s): + + Alternatively, the contents of this file may be used under the terms of + either the GNU General Public License Version 2 or later (the "GPL"), or + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the GPL or the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of either the GPL or the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the GPL or the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the MPL, the GPL or the LGPL. + + ***** END LICENSE BLOCK ***** + + + Version 1.18 + - initial release + - removed from SynCommons.pas, for better code clarity, and to reduce the + number of source code lines of the unit, and circumvent the Delphi 5/6/7 + limitation of 65535 lines (internal error PRO-3006) + + +*) + +interface + +{$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 + +uses + {$ifdef MSWINDOWS} + Windows, + {$else} + {$ifdef KYLIX3} + Types, + LibC, + SynKylix, + {$endif KYLIX3} + {$ifdef FPC} + Unix, + {$endif FPC} + {$endif MSWINDOWS} + SysUtils, + Classes, + {$ifndef LVCL} + SyncObjs, // for TEvent and TCriticalSection + Contnrs, // for TObjectList + {$endif} + {$ifndef NOVARIANTS} + Variants, + {$endif} + SynCommons; + + + +{ ************ filtering and validation classes and functions ************** } + +/// return TRUE if the supplied content is a valid email address +// - follows RFC 822, to validate local-part@domain email format +function IsValidEmail(P: PUTF8Char): boolean; + +/// return TRUE if the supplied content is a valid IP v4 address +function IsValidIP4Address(P: PUTF8Char): boolean; + +/// return TRUE if the supplied content matchs a glob pattern +// - ? Matches any single characer +// - * Matches any contiguous characters +// - [abc] Matches a or b or c at that position +// - [^abc] Matches anything but a or b or c at that position +// - [!abc] Matches anything but a or b or c at that position +// - [a-e] Matches a through e at that position +// - [abcx-z] Matches a or b or c or x or y or or z, as does [a-cx-z] +// - 'ma?ch.*' would match match.exe, mavch.dat, march.on, etc.. +// - 'this [e-n]s a [!zy]est' would match 'this is a test', but would not +// match 'this as a test' nor 'this is a zest' +// - consider using TMatch or TMatchs if you expect to reuse the pattern +function IsMatch(const Pattern, Text: RawUTF8; CaseInsensitive: boolean=false): boolean; + +/// return TRUE if the supplied content matchs a glob pattern, using VCL strings +// - is a wrapper around IsMatch() with fast UTF-8 conversion +function IsMatchString(const Pattern, Text: string; CaseInsensitive: boolean=false): boolean; + +type + PMatch = ^TMatch; + TMatchSearchFunction = function(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; + + /// low-level structure used by IsMatch() for actual glob search + // - you can use this object to prepare a given pattern, e.g. in a loop + // - implemented as a fast brute-force state-machine without any heap allocation + // - some common patterns ('exactmatch', 'startwith*', '*endwith', '*contained*') + // are handled with dedicated code, optionally with case-insensitive search + // - consider using TMatchs (or SetMatchs/TMatchDynArray) if you expect to + // search for several patterns, or even TExprParserMatch for expression search + {$ifdef UNICODE}TMatch = record{$else}TMatch = object{$endif} + private + Pattern, Text: PUTF8Char; + P, T, PMax, TMax: PtrInt; + Upper: PNormTable; + State: (sNONE, sABORT, sEND, sLITERAL, sPATTERN, sRANGE, sVALID); + procedure MatchAfterStar; + procedure MatchMain; + public + /// published for proper inlining + Search: TMatchSearchFunction; + /// initialize the internal fields for a given glob search pattern + // - note that the aPattern instance should remain in memory, since it will + // be pointed to by the Pattern private field of this object + procedure Prepare(const aPattern: RawUTF8; aCaseInsensitive, aReuse: boolean); overload; + /// initialize the internal fields for a given glob search pattern + // - note that the aPattern buffer should remain in memory, since it will + // be pointed to by the Pattern private field of this object + procedure Prepare(aPattern: PUTF8Char; aPatternLen: integer; + aCaseInsensitive, aReuse: boolean); overload; + /// initialize low-level internal fields for'*aPattern*' search + // - this method is faster than a regular Prepare('*' + aPattern + '*') + // - warning: the supplied aPattern variable may be modified in-place to be + // filled with some lookup buffer, for length(aPattern) in [2..31] range + procedure PrepareContains(var aPattern: RawUTF8; aCaseInsensitive: boolean); overload; + /// initialize low-level internal fields for a custom search algorithm + procedure PrepareRaw(aPattern: PUTF8Char; aPatternLen: integer; + aSearch: TMatchSearchFunction); + /// returns TRUE if the supplied content matches the prepared glob pattern + // - this method is not thread-safe + function Match(const aText: RawUTF8): boolean; overload; + {$ifdef FPC}inline;{$endif} + /// returns TRUE if the supplied content matches the prepared glob pattern + // - this method is not thread-safe + function Match(aText: PUTF8Char; aTextLen: PtrInt): boolean; overload; + {$ifdef HASINLINE}inline;{$endif} + /// returns TRUE if the supplied content matches the prepared glob pattern + // - this method IS thread-safe, and won't lock + function MatchThreadSafe(const aText: RawUTF8): boolean; + /// returns TRUE if the supplied VCL/LCL content matches the prepared glob pattern + // - this method IS thread-safe, will use stack to UTF-8 temporary conversion + // if possible, and won't lock + function MatchString(const aText: string): boolean; + /// returns TRUE if this search pattern matches another + function Equals(const aAnother{$ifndef DELPHI5OROLDER}: TMatch{$endif}): boolean; + {$ifdef HASINLINE}inline;{$endif} + /// access to the pattern length as stored in PMax + 1 + function PatternLength: integer; {$ifdef HASINLINE}inline;{$endif} + /// access to the pattern text as stored in Pattern + function PatternText: PUTF8Char; {$ifdef HASINLINE}inline;{$endif} + end; + /// use SetMatchs() to initialize such an array from a CSV pattern text + TMatchDynArray = array of TMatch; + + /// TMatch descendant owning a copy of the Pattern string to avoid GPF issues + TMatchStore = record + /// access to the research criteria + // - defined as a nested record (and not an object) to circumvent Delphi bug + Pattern: TMatch; + /// Pattern.Pattern PUTF8Char will point to this instance + PatternInstance: RawUTF8; + end; + TMatchStoreDynArray = array of TMatchStore; + + /// stores several TMatch instances, from a set of glob patterns + TMatchs = class(TSynPersistent) + protected + fMatch: TMatchStoreDynArray; + fMatchCount: integer; + public + /// add once some glob patterns to the internal TMach list + // - aPatterns[] follows the IsMatch() syntax + constructor Create(const aPatterns: TRawUTF8DynArray; CaseInsensitive: Boolean); reintroduce; overload; + /// add once some glob patterns to the internal TMach list + // - aPatterns[] follows the IsMatch() syntax + procedure Subscribe(const aPatterns: TRawUTF8DynArray; CaseInsensitive: Boolean); overload; virtual; + /// add once some glob patterns to the internal TMach list + // - each CSV item in aPatterns follows the IsMatch() syntax + procedure Subscribe(const aPatternsCSV: RawUTF8; CaseInsensitive: Boolean); overload; + /// search patterns in the supplied UTF-8 text + // - returns -1 if no filter has been subscribed + // - returns -2 if there is no match on any previous pattern subscription + // - returns fMatch[] index, i.e. >= 0 number on first matching pattern + // - this method is thread-safe + function Match(const aText: RawUTF8): integer; overload; {$ifdef HASINLINE}inline;{$endif} + /// search patterns in the supplied UTF-8 text buffer + function Match(aText: PUTF8Char; aLen: integer): integer; overload; + /// search patterns in the supplied VCL/LCL text + // - could be used on a TFileName for instance + // - will avoid any memory allocation if aText is small enough + function MatchString(const aText: string): integer; + end; + +/// fill the Match[] dynamic array with all glob patterns supplied as CSV +// - returns how many patterns have been set in Match[|] +// - note that the CSVPattern instance should remain in memory, since it will +// be pointed to by the Match[].Pattern private field +function SetMatchs(const CSVPattern: RawUTF8; CaseInsensitive: boolean; + out Match: TMatchDynArray): integer; overload; + +/// fill the Match[0..MatchMax] static array with all glob patterns supplied as CSV +// - note that the CSVPattern instance should remain in memory, since it will +// be pointed to by the Match[].Pattern private field +function SetMatchs(CSVPattern: PUTF8Char; CaseInsensitive: boolean; + Match: PMatch; MatchMax: integer): integer; overload; + +/// search if one TMach is already registered in the Several[] dynamic array +function MatchExists(const One: TMatch; const Several: TMatchDynArray): boolean; + +/// add one TMach if not already registered in the Several[] dynamic array +function MatchAdd(const One: TMatch; var Several: TMatchDynArray): boolean; + +/// returns TRUE if Match=nil or if any Match[].Match(Text) is TRUE +function MatchAny(const Match: TMatchDynArray; const Text: RawUTF8): boolean; + +/// apply the CSV-supplied glob patterns to an array of RawUTF8 +// - any text not maching the pattern will be deleted from the array +procedure FilterMatchs(const CSVPattern: RawUTF8; CaseInsensitive: boolean; + var Values: TRawUTF8DynArray); + + +type + TSynFilterOrValidate = class; + + TSynFilterOrValidateObjArray = array of TSynFilterOrValidate; + TSynFilterOrValidateObjArrayArray = array of TSynFilterOrValidateObjArray; + + /// will define a filter (transformation) or a validation process to be + // applied to a database Record content (typicaly a TSQLRecord) + // - the optional associated parameters are to be supplied JSON-encoded + TSynFilterOrValidate = class + protected + fParameters: RawUTF8; + /// children must override this method in order to parse the JSON-encoded + // parameters, and store it in protected field values + procedure SetParameters(const Value: RawUTF8); virtual; + public + /// add the filter or validation process to a list, checking if not present + // - if an instance with the same class type and parameters is already + // registered, will call aInstance.Free and return the exising instance + // - if there is no similar instance, will add it to the list and return it + function AddOnce(var aObjArray: TSynFilterOrValidateObjArray; + aFreeIfAlreadyThere: boolean=true): TSynFilterOrValidate; + public + /// initialize the filter (transformation) or validation instance + // - most of the time, optional parameters may be specified as JSON, + // possibly with the extended MongoDB syntax + constructor Create(const aParameters: RawUTF8=''); overload; virtual; + /// initialize the filter or validation instance + /// - this overloaded constructor will allow to easily set the parameters + constructor CreateUTF8(const Format: RawUTF8; const Args, Params: array of const); overload; + /// the optional associated parameters, supplied as JSON-encoded + property Parameters: RawUTF8 read fParameters write SetParameters; + end; + + /// will define a validation to be applied to a Record (typicaly a TSQLRecord) + // field content + // - a typical usage is to validate an email or IP adress e.g. + // - the optional associated parameters are to be supplied JSON-encoded + TSynValidate = class(TSynFilterOrValidate) + public + /// perform the validation action to the specified value + // - the value is expected by be UTF-8 text, as generated by + // TPropInfo.GetValue e.g. + // - if the validation failed, must return FALSE and put some message in + // ErrorMsg (translated into the current language: you could e.g. use + // a resourcestring and a SysUtils.Format() call for automatic translation + // via the mORMoti18n unit - you can leave ErrorMsg='' to trigger a + // generic error message from clas name ('"Validate email" rule failed' + // for TSynValidateEmail class e.g.) + // - if the validation passed, will return TRUE + function Process(FieldIndex: integer; const Value: RawUTF8; var ErrorMsg: string): boolean; + virtual; abstract; + end; + + /// points to a TSynValidate variable + // - used e.g. as optional parameter to TSQLRecord.Validate/FilterAndValidate + PSynValidate = ^TSynValidate; + + /// IP v4 address validation to be applied to a Record field content + // (typicaly a TSQLRecord) + // - this versions expect no parameter + TSynValidateIPAddress = class(TSynValidate) + protected + public + /// perform the IP Address validation action to the specified value + function Process(aFieldIndex: integer; const Value: RawUTF8; + var ErrorMsg: string): boolean; override; + end; + + /// IP address validation to be applied to a Record field content + // (typicaly a TSQLRecord) + // - optional JSON encoded parameters are "AllowedTLD" or "ForbiddenTLD", + // expecting a CSV lis of Top-Level-Domain (TLD) names, e.g. + // $ '{"AllowedTLD":"com,org,net","ForbiddenTLD":"fr"}' + // $ '{AnyTLD:true,ForbiddenDomains:"mailinator.com,yopmail.com"}' + // - this will process a validation according to RFC 822 (calling the + // IsValidEmail() function) then will check for the TLD to be in one of + // the Top-Level domains ('.com' and such) or a two-char country, and + // then will check the TLD according to AllowedTLD and ForbiddenTLD + TSynValidateEmail = class(TSynValidate) + private + fAllowedTLD: RawUTF8; + fForbiddenTLD: RawUTF8; + fForbiddenDomains: RawUTF8; + fAnyTLD: boolean; + protected + /// decode all published properties from their JSON representation + procedure SetParameters(const Value: RawUTF8); override; + public + /// perform the Email Address validation action to the specified value + // - call IsValidEmail() function and check for the supplied TLD + function Process(aFieldIndex: integer; const Value: RawUTF8; var ErrorMsg: string): boolean; override; + /// allow any TLD to be allowed, even if not a generic TLD (.com,.net ...) + // - this may be mandatory since already over 1,300 new gTLD names or + // "strings" could become available in the next few years: there is a + // growing list of new gTLDs available at + // @http://newgtlds.icann.org/en/program-status/delegated-strings + // - the only restriction is that it should be ascii characters + property AnyTLD: boolean read fAnyTLD write fAnyTLD; + /// a CSV list of allowed TLD + // - if accessed directly, should be set as lower case values + // - e.g. 'com,org,net' + property AllowedTLD: RawUTF8 read fAllowedTLD write fAllowedTLD; + /// a CSV list of forbidden TLD + // - if accessed directly, should be set as lower case values + // - e.g. 'fr' + property ForbiddenTLD: RawUTF8 read fForbiddenTLD write fForbiddenTLD; + /// a CSV list of forbidden domain names + // - if accessed directly, should be set as lower case values + // - not only the TLD, but whole domains like 'cracks.ru,hotmail.com' or such + property ForbiddenDomains: RawUTF8 read fForbiddenDomains write fForbiddenDomains; + end; + + /// glob case-sensitive pattern validation of a Record field content + // - parameter is NOT JSON encoded, but is some basic TMatch glob pattern + // - ? Matches any single characer + // - * Matches any contiguous characters + // - [abc] Matches a or b or c at that position + // - [^abc] Matches anything but a or b or c at that position + // - [!abc] Matches anything but a or b or c at that position + // - [a-e] Matches a through e at that position + // - [abcx-z] Matches a or b or c or x or y or or z, as does [a-cx-z] + // - 'ma?ch.*' would match match.exe, mavch.dat, march.on, etc.. + // - 'this [e-n]s a [!zy]est' would match 'this is a test', but would not + // match 'this as a test' nor 'this is a zest' + // - pattern check IS case sensitive (TSynValidatePatternI is not) + // - this class is not as complete as PCRE regex for example, + // but code overhead is very small, and speed good enough in practice + TSynValidatePattern = class(TSynValidate) + protected + fMatch: TMatch; + procedure SetParameters(const Value: RawUTF8); override; + public + /// perform the pattern validation to the specified value + // - pattern can be e.g. '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]' + // - this method will implement both TSynValidatePattern and + // TSynValidatePatternI, checking the current class + function Process(aFieldIndex: integer; const Value: RawUTF8; + var ErrorMsg: string): boolean; override; + end; + + /// glob case-insensitive pattern validation of a text field content + // (typicaly a TSQLRecord) + // - parameter is NOT JSON encoded, but is some basic TMatch glob pattern + // - same as TSynValidatePattern, but is NOT case sensitive + TSynValidatePatternI = class(TSynValidatePattern); + + /// text validation to ensure that to any text field would not be '' + TSynValidateNonVoidText = class(TSynValidate) + public + /// perform the non void text validation action to the specified value + function Process(aFieldIndex: integer; const Value: RawUTF8; + var ErrorMsg: string): boolean; override; + end; + + TSynValidateTextProps = array[0..15] of cardinal; + +{$M+} // to have existing RTTI for published properties + /// text validation to be applied to any Record field content + // - default MinLength value is 1, MaxLength is maxInt: so a blank + // TSynValidateText.Create('') is the same as TSynValidateNonVoidText + // - MinAlphaCount, MinDigitCount, MinPunctCount, MinLowerCount and + // MinUpperCount allow you to specify the minimal count of respectively + // alphabetical [a-zA-Z], digit [0-9], punctuation [_!;.,/:?%$="#@(){}+-*], + // lower case or upper case characters + // - expects optional JSON parameters of the allowed text length range as + // $ '{"MinLength":5,"MaxLength":10,"MinAlphaCount":1,"MinDigitCount":1, + // $ "MinPunctCount":1,"MinLowerCount":1,"MinUpperCount":1} + TSynValidateText = class(TSynValidate) + private + /// used to store all associated validation properties by index + fProps: TSynValidateTextProps; + fUTF8Length: boolean; + protected + /// use sInvalidTextChar resourcestring to create a translated error message + procedure SetErrorMsg(fPropsIndex, InvalidTextIndex, MainIndex: integer; + var result: string); + /// decode "MinLength", "MaxLength", and other parameters into fProps[] + procedure SetParameters(const Value: RawUTF8); override; + public + /// perform the text length validation action to the specified value + function Process(aFieldIndex: integer; const Value: RawUTF8; + var ErrorMsg: string): boolean; override; + published + /// Minimal length value allowed for the text content + // - the length is calculated with UTF-16 Unicode codepoints, unless + // UTF8Length has been set to TRUE so that the UTF-8 byte count is checked + // - default is 1, i.e. a void text will not pass the validation + property MinLength: cardinal read fProps[0] write fProps[0]; + /// Maximal length value allowed for the text content + // - the length is calculated with UTF-16 Unicode codepoints, unless + // UTF8Length has been set to TRUE so that the UTF-8 byte count is checked + // - default is maxInt, i.e. no maximum length is set + property MaxLength: cardinal read fProps[1] write fProps[1]; + /// Minimal alphabetical character [a-zA-Z] count + // - default is 0, i.e. no minimum set + property MinAlphaCount: cardinal read fProps[2] write fProps[2]; + /// Maximal alphabetical character [a-zA-Z] count + // - default is maxInt, i.e. no Maximum set + property MaxAlphaCount: cardinal read fProps[10] write fProps[10]; + /// Minimal digit character [0-9] count + // - default is 0, i.e. no minimum set + property MinDigitCount: cardinal read fProps[3] write fProps[3]; + /// Maximal digit character [0-9] count + // - default is maxInt, i.e. no Maximum set + property MaxDigitCount: cardinal read fProps[11] write fProps[11]; + /// Minimal punctuation sign [_!;.,/:?%$="#@(){}+-*] count + // - default is 0, i.e. no minimum set + property MinPunctCount: cardinal read fProps[4] write fProps[4]; + /// Maximal punctuation sign [_!;.,/:?%$="#@(){}+-*] count + // - default is maxInt, i.e. no Maximum set + property MaxPunctCount: cardinal read fProps[12] write fProps[12]; + /// Minimal alphabetical lower case character [a-z] count + // - default is 0, i.e. no minimum set + property MinLowerCount: cardinal read fProps[5] write fProps[5]; + /// Maximal alphabetical lower case character [a-z] count + // - default is maxInt, i.e. no Maximum set + property MaxLowerCount: cardinal read fProps[13] write fProps[13]; + /// Minimal alphabetical upper case character [A-Z] count + // - default is 0, i.e. no minimum set + property MinUpperCount: cardinal read fProps[6] write fProps[6]; + /// Maximal alphabetical upper case character [A-Z] count + // - default is maxInt, i.e. no Maximum set + property MaxUpperCount: cardinal read fProps[14] write fProps[14]; + /// Minimal space count inside the value text + // - default is 0, i.e. any space number allowed + property MinSpaceCount: cardinal read fProps[7] write fProps[7]; + /// Maximal space count inside the value text + // - default is maxInt, i.e. any space number allowed + property MaxSpaceCount: cardinal read fProps[15] write fProps[15]; + /// Maximal space count allowed on the Left side + // - default is maxInt, i.e. any Left space allowed + property MaxLeftTrimCount: cardinal read fProps[8] write fProps[8]; + /// Maximal space count allowed on the Right side + // - default is maxInt, i.e. any Right space allowed + property MaxRightTrimCount: cardinal read fProps[9] write fProps[9]; + /// defines if lengths parameters expects UTF-8 or UTF-16 codepoints number + // - with default FALSE, the length is calculated with UTF-16 Unicode + // codepoints - MaxLength may not match the UCS4 glyphs number, in case of + // UTF-16 surrogates + // - you can set this property to TRUE so that the UTF-8 byte count would + // be used for truncation againts the MaxLength parameter + property UTF8Length: boolean read fUTF8Length write fUTF8Length; + end; +{$M-} + + /// strong password validation for a Record field content (typicaly a TSQLRecord) + // - the following parameters are set by default to + // $ '{"MinLength":5,"MaxLength":20,"MinAlphaCount":1,"MinDigitCount":1, + // $ "MinPunctCount":1,"MinLowerCount":1,"MinUpperCount":1,"MaxSpaceCount":0}' + // - you can specify some JSON encoded parameters to change this default + // values, which will validate the text field only if it contains from 5 to 10 + // characters, with at least one digit, one upper case letter, one lower case + // letter, and one ponctuation sign, with no space allowed inside + TSynValidatePassWord = class(TSynValidateText) + protected + /// set password specific parameters + procedure SetParameters(const Value: RawUTF8); override; + end; + + { C++Builder doesn't support array elements as properties (RSP-12595). + For now, simply exclude the relevant classes from C++Builder. } + {$NODEFINE TSynValidateTextProps} + {$NODEFINE TSynValidateText } + {$NODEFINE TSynValidatePassWord } + + /// will define a transformation to be applied to a Record field content + // (typicaly a TSQLRecord) + // - here "filter" means that content would be transformed according to a + // set of defined rules + // - a typical usage is to convert to lower or upper case, or + // trim any time or date value in a TDateTime field + // - the optional associated parameters are to be supplied JSON-encoded + TSynFilter = class(TSynFilterOrValidate) + protected + public + /// perform the transformation to the specified value + // - the value is converted into UTF-8 text, as expected by + // TPropInfo.GetValue / TPropInfo.SetValue e.g. + procedure Process(aFieldIndex: integer; var Value: RawUTF8); virtual; abstract; + end; + + /// class-refrence type (metaclass) for a TSynFilter or a TSynValidate + TSynFilterOrValidateClass = class of TSynFilterOrValidate; + + /// class-reference type (metaclass) of a record filter (transformation) + TSynFilterClass = class of TSynFilter; + + /// convert the value into ASCII Upper Case characters + // - UpperCase conversion is made for ASCII-7 only, i.e. 'a'..'z' characters + // - this version expects no parameter + TSynFilterUpperCase = class(TSynFilter) + public + /// perform the case conversion to the specified value + procedure Process(aFieldIndex: integer; var Value: RawUTF8); override; + end; + + /// convert the value into WinAnsi Upper Case characters + // - UpperCase conversion is made for all latin characters in the WinAnsi + // code page only, e.g. 'e' acute will be converted to 'E' + // - this version expects no parameter + TSynFilterUpperCaseU = class(TSynFilter) + public + /// perform the case conversion to the specified value + procedure Process(aFieldIndex: integer; var Value: RawUTF8); override; + end; + + /// convert the value into ASCII Lower Case characters + // - LowerCase conversion is made for ASCII-7 only, i.e. 'A'..'Z' characters + // - this version expects no parameter + TSynFilterLowerCase = class(TSynFilter) + public + /// perform the case conversion to the specified value + procedure Process(aFieldIndex: integer; var Value: RawUTF8); override; + end; + + /// convert the value into WinAnsi Lower Case characters + // - LowerCase conversion is made for all latin characters in the WinAnsi + // code page only, e.g. 'E' acute will be converted to 'e' + // - this version expects no parameter + TSynFilterLowerCaseU = class(TSynFilter) + public + /// perform the case conversion to the specified value + procedure Process(aFieldIndex: integer; var Value: RawUTF8); override; + end; + + /// trim any space character left or right to the value + // - this versions expect no parameter + TSynFilterTrim = class(TSynFilter) + public + /// perform the space triming conversion to the specified value + procedure Process(aFieldIndex: integer; var Value: RawUTF8); override; + end; + + /// truncate a text above a given maximum length + // - expects optional JSON parameters of the allowed text length range as + // $ '{MaxLength":10} + TSynFilterTruncate = class(TSynFilter) + protected + fMaxLength: cardinal; + fUTF8Length: boolean; + /// decode the MaxLength: and UTF8Length: parameters + procedure SetParameters(const Value: RawUTF8); override; + public + /// perform the length truncation of the specified value + procedure Process(aFieldIndex: integer; var Value: RawUTF8); override; + /// Maximal length value allowed for the text content + // - the length is calculated with UTF-16 Unicode codepoints, unless + // UTF8Length has been set to TRUE so that the UTF-8 byte count is checked + // - default is 0, i.e. no maximum length is forced + property MaxLength: cardinal read fMaxLength write fMaxLength; + /// defines if MaxLength is stored as UTF-8 or UTF-16 codepoints number + // - with default FALSE, the length is calculated with UTF-16 Unicode + // codepoints - MaxLength may not match the UCS4 glyphs number, in case of + // UTF-16 surrogates + // - you can set this property to TRUE so that the UTF-8 byte count would + // be used for truncation againts the MaxLength parameter + property UTF8Length: boolean read fUTF8Length write fUTF8Length; + end; + + +{ ************ Database types and classes ************************** } + +type + /// handled field/parameter/column types for abstract database access + // - this will map JSON-compatible low-level database-level access types, not + // high-level Delphi types as TSQLFieldType defined in mORMot.pas + // - it does not map either all potential types as defined in DB.pas (which + // are there for compatibility with old RDBMS, and are not abstract enough) + // - those types can be mapped to standard SQLite3 generic types, i.e. + // NULL, INTEGER, REAL, TEXT, BLOB (with the addition of a ftCurrency and + // ftDate type, for better support of most DB engines) + // see @http://www.sqlite.org/datatype3.html + // - the only string type handled here uses UTF-8 encoding (implemented + // using our RawUTF8 type), for cross-Delphi true Unicode process + TSQLDBFieldType = + (ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, ftDate, ftUTF8, ftBlob); + + /// set of field/parameter/column types for abstract database access + TSQLDBFieldTypes = set of TSQLDBFieldType; + + /// array of field/parameter/column types for abstract database access + TSQLDBFieldTypeDynArray = array of TSQLDBFieldType; + + /// array of field/parameter/column types for abstract database access + // - this array as a fixed size, ready to handle up to MAX_SQLFIELDS items + TSQLDBFieldTypeArray = array[0..MAX_SQLFIELDS-1] of TSQLDBFieldType; + + /// how TSQLVar may be processed + // - by default, ftDate will use seconds resolution unless svoDateWithMS is set + TSQLVarOption = (svoDateWithMS); + + /// defines how TSQLVar may be processed + TSQLVarOptions = set of TSQLVarOption; + + /// memory structure used for database values by reference storage + // - used mainly by SynDB, mORMot, mORMotDB and mORMotSQLite3 units + // - defines only TSQLDBFieldType data types (similar to those handled by + // SQLite3, with the addition of ftCurrency and ftDate) + // - cleaner/lighter dedicated type than TValue or variant/TVarData, strong + // enough to be marshalled as JSON content + // - variable-length data (e.g. UTF-8 text or binary BLOB) are never stored + // within this record, but VText/VBlob will point to an external (temporary) + // memory buffer + // - date/time is stored as ISO-8601 text (with milliseconds if svoDateWithMS + // option is set and the database supports it), and currency as double or BCD + // in most databases + TSQLVar = record + /// how this value should be processed + Options: TSQLVarOptions; + /// the type of the value stored + case VType: TSQLDBFieldType of + ftInt64: ( + VInt64: Int64); + ftDouble: ( + VDouble: double); + ftDate: ( + VDateTime: TDateTime); + ftCurrency: ( + VCurrency: Currency); + ftUTF8: ( + VText: PUTF8Char); + ftBlob: ( + VBlob: pointer; + VBlobLen: Integer) + end; + + /// dynamic array of database values by reference storage + TSQLVarDynArray = array of TSQLVar; + + /// used to store bit set for all available fields in a Table + // - with current MAX_SQLFIELDS value, 64 bits uses 8 bytes of memory + // - see also IsZero() and IsEqual() functions + // - you can also use ALL_FIELDS as defined in mORMot.pas + TSQLFieldBits = set of 0..MAX_SQLFIELDS-1; + + /// used to store a field index in a Table + // - note that -1 is commonly used for the ID/RowID field so the values should + // be signed + // - even if ShortInt (-128..127) may have been enough, we define a 16 bit + // safe unsigned integer to let the source compile with Delphi 5 + TSQLFieldIndex = SmallInt; // -32768..32767 + + /// used to store field indexes in a Table + // - same as TSQLFieldBits, but allowing to store the proper order + TSQLFieldIndexDynArray = array of TSQLFieldIndex; + + /// points to a bit set used for all available fields in a Table + PSQLFieldBits = ^TSQLFieldBits; + + /// generic parameter types, as recognized by SQLParamContent() and + // ExtractInlineParameters() functions + TSQLParamType = (sptUnknown, sptInteger, sptFloat, sptText, sptBlob, sptDateTime); + + /// array of parameter types, as recognized by SQLParamContent() and + // ExtractInlineParameters() functions + TSQLParamTypeDynArray = array of TSQLParamType; + + /// simple writer to a Stream, specialized for the JSON format and SQL export + // - use an internal buffer, faster than string+string + TJSONWriter = class(TTextWriter) + protected + /// used to store output format + fExpand: boolean; + /// used to store output format for TSQLRecord.GetJSONValues() + fWithID: boolean; + /// used to store field for TSQLRecord.GetJSONValues() + fFields: TSQLFieldIndexDynArray; + /// if not Expanded format, contains the Stream position of the first + // useful Row of data; i.e. ',val11' position in: + // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] } + fStartDataPosition: integer; + public + /// used internally to store column names and count for AddColumns + ColNames: TRawUTF8DynArray; + /// the data will be written to the specified Stream + // - if no Stream is supplied, a temporary memory stream will be created + // (it's faster to supply one, e.g. any TSQLRest.TempMemoryStream) + constructor Create(aStream: TStream; Expand, withID: boolean; + const Fields: TSQLFieldBits; aBufSize: integer=8192); overload; + /// the data will be written to the specified Stream + // - if no Stream is supplied, a temporary memory stream will be created + // (it's faster to supply one, e.g. any TSQLRest.TempMemoryStream) + constructor Create(aStream: TStream; Expand, withID: boolean; + const Fields: TSQLFieldIndexDynArray=nil; aBufSize: integer=8192); overload; + /// rewind the Stream position and write void JSON object + procedure CancelAllVoid; + /// write or init field names for appropriate JSON Expand later use + // - ColNames[] must have been initialized before calling this procedure + // - if aKnownRowsCount is not null, a "rowCount":... item will be added + // to the generated JSON stream (for faster unserialization of huge content) + procedure AddColumns(aKnownRowsCount: integer=0); + /// allow to change on the fly an expanded format column layout + // - by definition, a non expanded format will raise a ESynException + // - caller should then set ColNames[] and run AddColumns() + procedure ChangeExpandedFields(aWithID: boolean; const aFields: TSQLFieldIndexDynArray); overload; + /// end the serialized JSON object + // - cancel last ',' + // - close the JSON object ']' or ']}' + // - write non expanded postlog (,"rowcount":...), if needed + // - flush the internal buffer content if aFlushFinal=true + procedure EndJSONObject(aKnownRowsCount,aRowsCount: integer; aFlushFinal: boolean=true); + {$ifdef HASINLINE}inline;{$endif} + /// the first data row is erased from the content + // - only works if the associated storage stream is TMemoryStream + // - expect not Expanded format + procedure TrimFirstRow; + /// is set to TRUE in case of Expanded format + property Expand: boolean read fExpand write fExpand; + /// is set to TRUE if the ID field must be appended to the resulting JSON + // - this field is used only by TSQLRecord.GetJSONValues + // - this field is ignored by TSQLTable.GetJSONValues + property WithID: boolean read fWithID; + /// Read-Only access to the field bits set for each column to be stored + property Fields: TSQLFieldIndexDynArray read fFields; + /// if not Expanded format, contains the Stream position of the first + // useful Row of data; i.e. ',val11' position in: + // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] } + property StartDataPosition: integer read fStartDataPosition; + end; + +/// returns TRUE if no bit inside this TSQLFieldBits is set +// - is optimized for 64, 128, 192 and 256 max bits count (i.e. MAX_SQLFIELDS) +// - will work also with any other value +function IsZero(const Fields: TSQLFieldBits): boolean; overload; + {$ifdef HASINLINE}inline;{$endif} + +/// fast comparison of two TSQLFieldBits values +// - is optimized for 64, 128, 192 and 256 max bits count (i.e. MAX_SQLFIELDS) +// - will work also with any other value +function IsEqual(const A,B: TSQLFieldBits): boolean; overload; + {$ifdef HASINLINE}inline;{$endif} + +/// fast initialize a TSQLFieldBits with 0 +// - is optimized for 64, 128, 192 and 256 max bits count (i.e. MAX_SQLFIELDS) +// - will work also with any other value +procedure FillZero(var Fields: TSQLFieldBits); overload; + {$ifdef HASINLINE}inline;{$endif} + +/// convert a TSQLFieldBits set of bits into an array of integers +procedure FieldBitsToIndex(const Fields: TSQLFieldBits; + var Index: TSQLFieldIndexDynArray; MaxLength: integer=MAX_SQLFIELDS; + IndexStart: integer=0); overload; + +/// convert a TSQLFieldBits set of bits into an array of integers +function FieldBitsToIndex(const Fields: TSQLFieldBits; + MaxLength: integer=MAX_SQLFIELDS): TSQLFieldIndexDynArray; overload; + {$ifdef HASINLINE}inline;{$endif} + +/// add a field index to an array of field indexes +// - returns the index in Indexes[] of the newly appended Field value +function AddFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer; + +/// convert an array of field indexes into a TSQLFieldBits set of bits +procedure FieldIndexToBits(const Index: TSQLFieldIndexDynArray; var Fields: TSQLFieldBits); overload; + +// search a field index in an array of field indexes +// - returns the index in Indexes[] of the given Field value, -1 if not found +function SearchFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer; + +/// convert an array of field indexes into a TSQLFieldBits set of bits +function FieldIndexToBits(const Index: TSQLFieldIndexDynArray): TSQLFieldBits; overload; + {$ifdef HASINLINE}inline;{$endif} + +/// returns the stored size of a TSQLVar database value +// - only returns VBlobLen / StrLen(VText) size, 0 otherwise +function SQLVarLength(const Value: TSQLVar): integer; + +{$ifndef NOVARIANTS} + +/// convert any Variant into a database value +// - ftBlob kind won't be handled by this function +// - complex variant types would be converted into ftUTF8 JSON object/array +procedure VariantToSQLVar(const Input: variant; var temp: RawByteString; + var Output: TSQLVar); + +/// guess the correct TSQLDBFieldType from a variant value +function VariantTypeToSQLDBFieldType(const V: Variant): TSQLDBFieldType; + +{$endif NOVARIANTS} + + /// convert a date to a ISO-8601 string format for SQL '?' inlined parameters +// - will return the date encoded as '\uFFF1YYYY-MM-DD' - therefore +// ':("\uFFF12012-05-04"):' pattern will be recognized as a sftDateTime +// inline parameter in SQLParamContent() / ExtractInlineParameters() functions +// (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern) +// - to be used e.g. as in: +// ! aRec.CreateAndFillPrepare(Client,'Datum=?',[DateToSQL(EncodeDate(2012,5,4))]); +function DateToSQL(Date: TDateTime): RawUTF8; overload; + +/// convert a date to a ISO-8601 string format for SQL '?' inlined parameters +// - will return the date encoded as '\uFFF1YYYY-MM-DD' - therefore +// ':("\uFFF12012-05-04"):' pattern will be recognized as a sftDateTime +// inline parameter in SQLParamContent() / ExtractInlineParameters() functions +// (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern) +// - to be used e.g. as in: +// ! aRec.CreateAndFillPrepare(Client,'Datum=?',[DateToSQL(2012,5,4)]); +function DateToSQL(Year,Month,Day: cardinal): RawUTF8; overload; + +/// convert a date/time to a ISO-8601 string format for SQL '?' inlined parameters +// - if DT=0, returns '' +// - if DT contains only a date, returns the date encoded as '\uFFF1YYYY-MM-DD' +// - if DT contains only a time, returns the time encoded as '\uFFF1Thh:mm:ss' +// - otherwise, returns the ISO-8601 date and time encoded as '\uFFF1YYYY-MM-DDThh:mm:ss' +// (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern) +// - if WithMS is TRUE, will append '.sss' for milliseconds resolution +// - to be used e.g. as in: +// ! aRec.CreateAndFillPrepare(Client,'Datum<=?',[DateTimeToSQL(Now)]); +// - see TimeLogToSQL() if you are using TTimeLog/TModTime/TCreateTime values +function DateTimeToSQL(DT: TDateTime; WithMS: boolean=false): RawUTF8; + +/// decode a SQL '?' inlined parameter (i.e. with JSON_SQLDATE_MAGIC prefix) +// - as generated by DateToSQL/DateTimeToSQL/TimeLogToSQL functions +function SQLToDateTime(const ParamValueWithMagic: RawUTF8): TDateTime; + +/// convert a TTimeLog value into a ISO-8601 string format for SQL '?' inlined +// parameters +// - handle TTimeLog bit-encoded Int64 format +// - follows the same pattern as DateToSQL or DateTimeToSQL functions, i.e. +// will return the date or time encoded as '\uFFF1YYYY-MM-DDThh:mm:ss' - +// therefore ':("\uFFF12012-05-04T20:12:13"):' pattern will be recognized as a +// sftDateTime inline parameter in SQLParamContent() / ExtractInlineParameters() +// (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern) +// - to be used e.g. as in: +// ! aRec.CreateAndFillPrepare(Client,'Datum<=?',[TimeLogToSQL(TimeLogNow)]); +function TimeLogToSQL(const Timestamp: TTimeLog): RawUTF8; + +/// convert a Iso8601 encoded string into a ISO-8601 string format for SQL +// '?' inlined parameters +// - follows the same pattern as DateToSQL or DateTimeToSQL functions, i.e. +// will return the date or time encoded as '\uFFF1YYYY-MM-DDThh:mm:ss' - +// therefore ':("\uFFF12012-05-04T20:12:13"):' pattern will be recognized as a +// sftDateTime inline parameter in SQLParamContent() / ExtractInlineParameters() +// (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern) +// - in practice, just append the JSON_SQLDATE_MAGIC prefix to the supplied text +function Iso8601ToSQL(const S: RawByteString): RawUTF8; + + +/// guess the content type of an UTF-8 SQL value, in :(....): format +// - will be used e.g. by ExtractInlineParameters() to un-inline a SQL statement +// - sftInteger is returned for an INTEGER value, e.g. :(1234): +// - sftFloat is returned for any floating point value (i.e. some digits +// separated by a '.' character), e.g. :(12.34): or :(12E-34): +// - sftUTF8Text is returned for :("text"): or :('text'):, with double quoting +// inside the value +// - sftBlob will be recognized from the ':("\uFFF0base64encodedbinary"):' +// pattern, and return raw binary (for direct blob parameter assignment) +// - sftDateTime will be recognized from ':(\uFFF1"2012-05-04"):' pattern, +// i.e. JSON_SQLDATE_MAGIC-prefixed string as returned by DateToSQL() or +// DateTimeToSQL() functions +// - sftUnknown is returned on invalid content, or if wasNull is set to TRUE +// - if ParamValue is not nil, the pointing RawUTF8 string is set with the +// value inside :(...): without double quoting in case of sftUTF8Text +// - wasNull is set to TRUE if P was ':(null):' and ParamType is sftUnknwown +function SQLParamContent(P: PUTF8Char; out ParamType: TSQLParamType; out ParamValue: RawUTF8; + out wasNull: boolean): PUTF8Char; + +/// this function will extract inlined :(1234): parameters into Types[]/Values[] +// - will return the generic SQL statement with ? place holders for inlined +// parameters and setting Values with SQLParamContent() decoded content +// - will set maxParam=0 in case of no inlined parameters +// - recognized types are sptInteger, sptFloat, sptDateTime ('\uFFF1...'), +// sptUTF8Text and sptBlob ('\uFFF0...') +// - sptUnknown is returned on invalid content +function ExtractInlineParameters(const SQL: RawUTF8; + var Types: TSQLParamTypeDynArray; var Values: TRawUTF8DynArray; + var maxParam: integer; var Nulls: TSQLFieldBits): RawUTF8; + +/// returns a 64-bit value as inlined ':(1234):' text +function InlineParameter(ID: Int64): shortstring; overload; + +/// returns a string value as inlined ':("value"):' text +function InlineParameter(const value: RawUTF8): RawUTF8; overload; + +type + /// SQL Query comparison operators + // - used e.g. by CompareOperator() functions in SynTable.pas or vt_BestIndex() + // in mORMotSQLite3.pas + TCompareOperator = ( + soEqualTo, + soNotEqualTo, + soLessThan, + soLessThanOrEqualTo, + soGreaterThan, + soGreaterThanOrEqualTo, + soBeginWith, + soContains, + soSoundsLikeEnglish, + soSoundsLikeFrench, + soSoundsLikeSpanish); + +const + /// convert identified field types into high-level ORM types + // - as will be implemented in unit mORMot.pas + SQLDBFIELDTYPE_TO_DELPHITYPE: array[TSQLDBFieldType] of RawUTF8 = ( + '???','???', 'Int64', 'Double', 'Currency', 'TDateTime', 'RawUTF8', 'TSQLRawBlob'); + + + +{ ************ TSynTable types and classes ************************** } + +{$define SORTCOMPAREMETHOD} +{ if defined, the field content comparison will use a method instead of fixed + functions - could be mandatory for tftArray field kind } + +type + /// exception raised by all TSynTable related code + ETableDataException = class(ESynException); + + /// the available types for any TSynTable field property + // - this is used in our so-called SBF compact binary format + // (similar to BSON or Protocol Buffers) + // - those types are used for both storage and JSON conversion + // - basic types are similar to SQLite3, i.e. Int64/Double/UTF-8/Blob + // - storage can be of fixed size, or of variable length + // - you can specify to use WinAnsi encoding instead of UTF-8 for string storage + // (it can use less space on disk than UTF-8 encoding) + // - BLOB fields can be either internal (i.e. handled by TSynTable like a + // RawByteString text storage), either external (i.e. must be stored in a dedicated + // storage structure - e.g. another TSynBigTable instance) + TSynTableFieldType = + (// unknown or not defined field type + tftUnknown, + // some fixed-size field value + tftBoolean, tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64, + tftCurrency, tftDouble, + // some variable-size field value + tftVarUInt32, tftVarInt32, tftVarUInt64, + // text storage + tftWinAnsi, tftUTF8, + // BLOB fields + tftBlobInternal, tftBlobExternal, + // other variable-size field value + tftVarInt64); + + /// set of available field types for TSynTable + TSynTableFieldTypes = set of TSynTableFieldType; + + /// available option types for a field property + // - tfoIndex is set if an index must be created for this field + // - tfoUnique is set if field values must be unique (if set, the tfoIndex + // will be always forced) + // - tfoCaseInsensitive can be set to make no difference between 'a' and 'A' + // (by default, comparison is case-sensitive) - this option has an effect + // not only if tfoIndex or tfoUnique is set, but also for iterating search + TSynTableFieldOption = ( + tfoIndex, tfoUnique, tfoCaseInsensitive); + + /// set of option types for a field + TSynTableFieldOptions = set of TSynTableFieldOption; + + /// used to store bit set for all available fiels in a Table + // - with current format, maximum field count is 64 + TSynTableFieldBits = set of 0..63; + + /// an custom RawByteString type used to store internaly a data in + // our SBF compact binary format + TSBFString = type RawByteString; + + /// function prototype used to retrieve the index of a specified property name + // - 'ID' is handled separately: here must be available only the custom fields + TSynTableFieldIndex = function(const PropName: RawUTF8): integer of object; + + /// the recognized operators for a TSynTableStatement where clause + TSynTableStatementOperator = ( + opEqualTo, + opNotEqualTo, + opLessThan, + opLessThanOrEqualTo, + opGreaterThan, + opGreaterThanOrEqualTo, + opIn, + opIsNull, + opIsNotNull, + opLike, + opContains, + opFunction); + + TSynTableFieldProperties = class; + + /// one recognized SELECT expression for TSynTableStatement + TSynTableStatementSelect = record + /// the column SELECTed for the SQL statement, in the expected order + // - contains 0 for ID/RowID, or the RTTI field index + 1 + Field: integer; + /// an optional integer to be added + // - recognized from .. +123 .. -123 patterns in the select + ToBeAdded: integer; + /// the optional column alias, e.g. 'MaxID' for 'max(id) as MaxID' + Alias: RawUTF8; + /// the optional function applied to the SELECTed column + // - e.g. Max(RowID) would store 'Max' and SelectField[0]=0 + // - but Count(*) would store 'Count' and SelectField[0]=0, and + // set FunctionIsCountStart = TRUE + FunctionName: RawUTF8; + /// if the function needs a special process + // - e.g. funcCountStar for the special Count(*) expression or + // funcDistinct, funcMax for distinct(...)/max(...) aggregation + FunctionKnown: (funcNone, funcCountStar, funcDistinct, funcMax); + end; + + /// the recognized SELECT expressions for TSynTableStatement + TSynTableStatementSelectDynArray = array of TSynTableStatementSelect; + + /// one recognized WHERE expression for TSynTableStatement + TSynTableStatementWhere = record + /// any '(' before the actual expression + ParenthesisBefore: RawUTF8; + /// any ')' after the actual expression + ParenthesisAfter: RawUTF8; + /// expressions are evaluated as AND unless this field is set to TRUE + JoinedOR: boolean; + /// if this expression is preceded by a NOT modifier + NotClause: boolean; + /// the index of the field used for the WHERE expression + // - WhereField=0 for ID, 1 for field # 0, 2 for field #1, + // and so on... (i.e. WhereField = RTTI field index +1) + Field: integer; + /// the operator of the WHERE expression + Operator: TSynTableStatementOperator; + /// the SQL function name associated to a Field and Value + // - e.g. 'INTEGERDYNARRAYCONTAINS' and Field=0 for + // IntegerDynArrayContains(RowID,10) and ValueInteger=10 + // - Value does not contain anything + FunctionName: RawUTF8; + /// the value used for the WHERE expression + Value: RawUTF8; + /// the raw value SQL buffer used for the WHERE expression + ValueSQL: PUTF8Char; + /// the raw value SQL buffer length used for the WHERE expression + ValueSQLLen: integer; + /// an integer representation of WhereValue (used for ID check e.g.) + ValueInteger: integer; + /// used to fast compare with SBF binary compact formatted data + ValueSBF: TSBFString; + {$ifndef NOVARIANTS} + /// the value used for the WHERE expression, encoded as Variant + // - may be a TDocVariant for the IN operator + ValueVariant: variant; + {$endif} + end; + + /// the recognized WHERE expressions for TSynTableStatement + TSynTableStatementWhereDynArray = array of TSynTableStatementWhere; + + /// used to parse a SELECT SQL statement, following the SQlite3 syntax + // - handle basic REST commands, i.e. a SELECT over a single table (no JOIN) + // with its WHERE clause, and result column aliases + // - handle also aggregate functions like "SELECT Count(*) FROM TableName" + // - will also parse any LIMIT, OFFSET, ORDER BY, GROUP BY statement clause + TSynTableStatement = class + protected + fSQLStatement: RawUTF8; + fSelect: TSynTableStatementSelectDynArray; + fSelectFunctionCount: integer; + fTableName: RawUTF8; + fWhere: TSynTableStatementWhereDynArray; + fOrderByField: TSQLFieldIndexDynArray; + fGroupByField: TSQLFieldIndexDynArray; + fWhereHasParenthesis: boolean; + fOrderByDesc: boolean; + fLimit: integer; + fOffset: integer; + fWriter: TJSONWriter; + public + /// parse the given SELECT SQL statement and retrieve the corresponding + // parameters into this class read-only properties + // - the supplied GetFieldIndex() method is used to populate the + // SelectedFields and Where[].Field properties + // - SimpleFieldsBits is used for '*' field names + // - SQLStatement is left '' if the SQL statement is not correct + // - if SQLStatement is set, the caller must check for TableName to match + // the expected value, then use the Where[] to retrieve the content + // - if FieldProp is set, then the Where[].ValueSBF property is initialized + // with the SBF equivalence of the Where[].Value + constructor Create(const SQL: RawUTF8; GetFieldIndex: TSynTableFieldIndex; + SimpleFieldsBits: TSQLFieldBits=[0..MAX_SQLFIELDS-1]; + FieldProp: TSynTableFieldProperties=nil); + /// compute the SELECT column bits from the SelectFields array + procedure SelectFieldBits(var Fields: TSQLFieldBits; var withID: boolean); + + /// the SELECT SQL statement parsed + // - equals '' if the parsing failed + property SQLStatement: RawUTF8 read fSQLStatement; + /// the column SELECTed for the SQL statement, in the expected order + property Select: TSynTableStatementSelectDynArray read fSelect; + /// if the SELECTed expression of this SQL statement have any function defined + property SelectFunctionCount: integer read fSelectFunctionCount; + /// the retrieved table name + property TableName: RawUTF8 read fTableName; + /// the WHERE clause of this SQL statement + property Where: TSynTableStatementWhereDynArray read fWhere; + /// if the WHERE clause contains any ( ) parenthesis expression + property WhereHasParenthesis: boolean read fWhereHasParenthesis; + /// recognize an GROUP BY clause with one or several fields + // - here 0 = ID, otherwise RTTI field index +1 + property GroupByField: TSQLFieldIndexDynArray read fGroupByField; + /// recognize an ORDER BY clause with one or several fields + // - here 0 = ID, otherwise RTTI field index +1 + property OrderByField: TSQLFieldIndexDynArray read fOrderByField; + /// false for default ASC order, true for DESC attribute + property OrderByDesc: boolean read fOrderByDesc; + /// the number specified by the optional LIMIT ... clause + // - set to 0 by default (meaning no LIMIT clause) + property Limit: integer read fLimit; + /// the number specified by the optional OFFSET ... clause + // - set to 0 by default (meaning no OFFSET clause) + property Offset: integer read fOffset; + /// optional associated writer + property Writer: TJSONWriter read fWriter write fWriter; + end; + + /// function prototype used to retrieve the RECORD data of a specified Index + // - the index is not the per-ID index, but the "physical" index, i.e. the + // index value used to retrieve data from low-level (and faster) method + // - should return nil if Index is out of range + // - caller must provide a temporary storage buffer to be used optionally + TSynTableGetRecordData = function( + Index: integer; var aTempData: RawByteString): pointer of object; + + TSynTable = class; + + {$ifdef SORTCOMPAREMETHOD} + /// internal value used by TSynTableFieldProperties.SortCompare() method to + // avoid stack allocation + TSortCompareTmp = record + PB1, PB2: PByte; + L1,L2: integer; + end; + {$endif} + + /// store the type properties of a given field / database column + TSynTableFieldProperties = class + protected + /// used during OrderedIndexSort to prevent stack usage + SortPivot: pointer; + {$ifdef SORTCOMPAREMETHOD} + /// internal value used by SortCompare() method to avoid stack allocation + SortCompareTmp: TSortCompareTmp; + {$endif} + /// these two temporary buffers are used to call TSynTableGetRecordData + DataTemp1, DataTemp2: RawByteString; + /// the associated table which own this field property + Owner: TSynTable; + /// the global size of a default field value, as encoded + // in our SBF compact binary format + fDefaultFieldLength: integer; + /// a default field data, as encoded in our SBF compact binary format + fDefaultFieldData: TSBFString; + /// last >=0 value returned by the last OrderedIndexFindAdd() call + fOrderedIndexFindAdd: integer; + /// used for internal QuickSort of OrderedIndex[] + // - call SortCompare() for sorting the items + procedure OrderedIndexSort(L,R: PtrInt); + /// retrieve an index from OrderedIndex[] of the given value + // - call SortCompare() to compare to the reference value + function OrderedIndexFind(Value: pointer): PtrInt; + /// retrieve an index where a Value must be added into OrderedIndex[] + // - call SortCompare() to compare to the reference value + // - returns -1 if Value is there, or the index where to insert + // - the returned value (if >= 0) will be stored in fOrderedIndexFindAdd + function OrderedIndexFindAdd(Value: pointer): PtrInt; + /// set OrderedIndexReverse[OrderedIndex[aOrderedIndex]] := aOrderedIndex; + procedure OrderedIndexReverseSet(aOrderedIndex: integer); + public + /// the field name + Name: RawUTF8; + /// kind of field (defines both value type and storage to be used) + FieldType: TSynTableFieldType; + /// the fixed-length size, or -1 for a varInt, -2 for a variable string + FieldSize: integer; + /// options of this field + Options: TSynTableFieldOptions; + /// contains the offset of this field, in case of fixed-length field + // - normaly, fixed-length fields are stored in the beginning of the record + // storage: in this case, a value >= 0 will point to the position of the + // field value of this field + // - if the value is < 0, its absolute will be the field number to be counted + // after TSynTable.fFieldVariableOffset (-1 for first item) + Offset: integer; + /// number of the field in the table (starting at 0) + FieldNumber: integer; + /// if allocated, contains the storage indexes of every item, in sorted order + // - only available if tfoIndex is in Options + // - the index is not the per-ID index, but the "physical" index, i.e. the + // index value used to retrieve data from low-level (and faster) method + OrderedIndex: TIntegerDynArray; + /// if allocated, contains the reverse storage index of OrderedIndex + // - i.e. OrderedIndexReverse[OrderedIndex[i]] := i; + // - used to speed up the record update procedure with huge number of + // records + OrderedIndexReverse: TIntegerDynArray; + /// number of items in OrderedIndex[] + // - is set to 0 when the content has been modified (mark force recreate) + OrderedIndexCount: integer; + /// if set to TRUE after an OrderedIndex[] refresh but with not sorting + // - OrderedIndexSort(0,OrderedIndexCount-1) must be called before using + // the OrderedIndex[] array + // - you should call OrderedIndexRefresh method to ensure it is sorted + OrderedIndexNotSorted: boolean; + /// all TSynValidate instances registered per each field + Filters: TObjectList; + /// all TSynValidate instances registered per each field + Validates: TObjectList; + /// low-level binary comparison used by IDSort and TSynTable.IterateJSONValues + // - P1 and P2 must point to the values encoded in our SBF compact binary format + {$ifdef SORTCOMPAREMETHOD} + function SortCompare(P1,P2: PUTF8Char): PtrInt; + {$else} + SortCompare: TUTF8Compare; + {$endif} + + /// read entry from a specified file reader + constructor CreateFrom(var RD: TFileBufferReader); + /// release associated memory and objects + destructor Destroy; override; + /// save entry to a specified file writer + procedure SaveTo(WR: TFileBufferWriter); + /// decode the value from our SBF compact binary format into UTF-8 JSON + // - returns the next FieldBuffer value + function GetJSON(FieldBuffer: pointer; W: TTextWriter): pointer; + /// decode the value from our SBF compact binary format into UTF-8 text + // - this method does not check for FieldBuffer to be not nil -> caller + // should check this explicitely + function GetValue(FieldBuffer: pointer): RawUTF8; + /// decode the value from a record buffer into an Boolean + // - will call Owner.GetData to retrieve then decode the field SBF content + function GetBoolean(RecordBuffer: pointer): Boolean; + {$ifdef HASINLINE}inline;{$endif} + /// decode the value from a record buffer into an integer + // - will call Owner.GetData to retrieve then decode the field SBF content + function GetInteger(RecordBuffer: pointer): Integer; + /// decode the value from a record buffer into an Int64 + // - will call Owner.GetData to retrieve then decode the field SBF content + function GetInt64(RecordBuffer: pointer): Int64; + /// decode the value from a record buffer into an floating-point value + // - will call Owner.GetData to retrieve then decode the field SBF content + function GetDouble(RecordBuffer: pointer): Double; + /// decode the value from a record buffer into an currency value + // - will call Owner.GetData to retrieve then decode the field SBF content + function GetCurrency(RecordBuffer: pointer): Currency; + /// decode the value from a record buffer into a RawUTF8 string + // - will call Owner.GetData to retrieve then decode the field SBF content + function GetRawUTF8(RecordBuffer: pointer): RawUTF8; + {$ifndef NOVARIANTS} + /// decode the value from our SBF compact binary format into a Variant + function GetVariant(FieldBuffer: pointer): Variant; overload; + {$ifdef HASINLINE}inline;{$endif} + /// decode the value from our SBF compact binary format into a Variant + procedure GetVariant(FieldBuffer: pointer; var result: Variant); overload; + {$endif} + /// retrieve the binary length (in bytes) of some SBF compact binary format + function GetLength(FieldBuffer: pointer): Integer; + {$ifdef HASINLINE}inline;{$endif} + /// create some SBF compact binary format from a Delphi binary value + // - will return '' if the field type doesn't match a boolean + function SBF(const Value: Boolean): TSBFString; overload; + /// create some SBF compact binary format from a Delphi binary value + // - will encode any byte, word, integer, cardinal, Int64 value + // - will return '' if the field type doesn't match an integer + function SBF(const Value: Int64): TSBFString; overload; + /// create some SBF compact binary format from a Delphi binary value + // - will encode any byte, word, integer, cardinal value + // - will return '' if the field type doesn't match an integer + function SBF(const Value: Integer): TSBFString; overload; + /// create some SBF compact binary format from a Delphi binary value + // - will return '' if the field type doesn't match a currency + // - we can't use SBF() method name because of Currency/Double ambiguity + function SBFCurr(const Value: Currency): TSBFString; + /// create some SBF compact binary format from a Delphi binary value + // - will return '' if the field type doesn't match a floating-point + // - we can't use SBF() method name because of Currency/Double ambiguity + function SBFFloat(const Value: Double): TSBFString; + /// create some SBF compact binary format from a Delphi binary value + // - expect a RawUTF8 string: will be converted to WinAnsiString + // before storage, for tftWinAnsi + // - will return '' if the field type doesn't match a string + function SBF(const Value: RawUTF8): TSBFString; overload; + /// create some SBF compact binary format from a BLOB memory buffer + // - will return '' if the field type doesn't match tftBlobInternal + function SBF(Value: pointer; ValueLen: integer): TSBFString; overload; + /// convert any UTF-8 encoded value into our SBF compact binary format + // - can be used e.g. from a WHERE clause, for fast comparison in + // TSynTableStatement.WhereValue content using OrderedIndex[] + // - is the reverse of GetValue/GetRawUTF8 methods above + function SBFFromRawUTF8(const aValue: RawUTF8): TSBFString; + {$ifndef NOVARIANTS} + /// create some SBF compact binary format from a Variant value + function SBF(const Value: Variant): TSBFString; overload; + {$endif} + + /// will update then sort the array of indexes used for the field index + // - the OrderedIndex[] array is first refreshed according to the + // aOldIndex, aNewIndex parameters: aOldIndex=-1 for Add, aNewIndex=-1 for + // Delete, or both >= 0 for update + // - call with both indexes = -1 will sort the existing OrderedIndex[] array + // - GetData property must have been set with a method returning a pointer + // to the field data for a given index (this index is not the per-ID index, + // but the "physical" index, i.e. the index value used to retrieve data + // from low-level (and fast) GetData method) + // - aOldRecordData and aNewRecordData can be specified in order to guess + // if the field data has really been modified (speed up the update a lot + // to only sort indexed fields if its content has been really modified) + // - returns FALSE if any parameter is invalid + function OrderedIndexUpdate(aOldIndex, aNewIndex: integer; + aOldRecordData, aNewRecordData: pointer): boolean; + /// retrieve one or more "physical" indexes matching a WHERE Statement + // - is faster than O(1) GetIteraring(), because will use O(log(n)) binary + // search using the OrderedIndex[] array + // - returns the resulting indexes as a a sorted list in MatchIndex/MatchIndexCount + // - if the indexes are already present in the list, won't duplicate them + // - WhereSBFValue must be a valid SBF formated field buffer content + // - the Limit parameter is similar to the SQL LIMIT clause: if greater than 0, + // an upper bound on the number of rows returned is placed (e.g. set Limit=1 + // to only retrieve the first match) + // - GetData property must have been set with a method returning a pointer + // to the field data for a given index (this index is not the per-ID index, + // but the "physical" index, i.e. the index value used to retrieve data + // from low-level (and fast) GetData method) + // - in this method, indexes are not the per-ID indexes, but the "physical" + // indexes, i.e. each index value used to retrieve data from low-level + // (and fast) GetData method + function OrderedIndexMatch(WhereSBFValue: pointer; + var MatchIndex: TIntegerDynArray; var MatchIndexCount: integer; + Limit: Integer=0): Boolean; + /// will force refresh the OrderedIndex[] array + // - to be called e.g. if OrderedIndexNotSorted = TRUE, if you want to + // access to the OrderedIndex[] array + procedure OrderedIndexRefresh; + /// register a custom filter or validation rule to the class for this field + // - this will be used by Filter() and Validate() methods + // - will return the specified associated TSynFilterOrValidate instance + // - a TSynValidateTableUniqueField is always added by + // TSynTable.AfterFieldModif if tfoUnique is set in Options + function AddFilterOrValidate(aFilter: TSynFilterOrValidate): TSynFilterOrValidate; + /// check the registered constraints + // - returns '' on success + // - returns an error message e.g. if a tftUnique constraint failed + // - RecordIndex=-1 in case of adding, or the physical index of the updated record + function Validate(RecordBuffer: pointer; RecordIndex: integer): string; + /// some default SBF compact binary format content + property SBFDefault: TSBFString read fDefaultFieldData; + end; + + +{$ifndef DELPHI5OROLDER} + + /// a pointer to structure used to store a TSynTable record + PSynTableData = ^TSynTableData; + + {$A-} { packet object not allowed since Delphi 2009 :( } + /// used to store a TSynTable record using our SBF compact binary format + // - this object can be created on the stack + // - it is mapped into a variant TVarData, to be retrieved by the + // TSynTable.Data method - but direct allocation of a TSynTableData on the + // stack is faster (due to the Variant overhead) + // - is defined either as an object either as a record, due to a bug + // in Delphi 2009/2010 compiler (at least): this structure is not initialized + // if defined as an object on the stack, but will be as a record :( + {$ifdef UNICODE}TSynTableData = record{$else}TSynTableData = object{$endif UNICODE} + {$ifdef UNICODE}private{$else}protected{$endif UNICODE} + VType: TVarType; + Filler: array[1..SizeOf(TVarData)-SizeOf(TVarType)-SizeOf(pointer)*2-4] of byte; + VID: integer; + VTable: TSynTable; + VValue: TSBFString; + {$ifndef NOVARIANTS} + function GetFieldValue(const FieldName: RawUTF8): Variant; overload; + procedure GetFieldVariant(const FieldName: RawUTF8; var result: Variant); + procedure SetFieldValue(const FieldName: RawUTF8; const Value: Variant); overload; + {$endif} + /// raise an exception if VTable=nil + procedure CheckVTableInitialized; + {$ifdef HASINLINE}inline;{$endif} + public + /// initialize a record data content for a specified table + // - a void content is set + procedure Init(aTable: TSynTable; aID: Integer=0); overload; {$ifdef HASINLINE}inline;{$endif} + /// initialize a record data content for a specified table + // - the specified SBF content is store inside this TSynTableData + procedure Init(aTable: TSynTable; aID: Integer; RecordBuffer: pointer; + RecordBufferLen: integer); overload; + /// the associated record ID + property ID: integer read VID write VID; + /// the associated TSynTable instance + property Table: TSynTable read VTable write VTable; + /// the record content, SBF compact binary format encoded + property SBF: TSBFString read VValue; + {$ifndef NOVARIANTS} + /// set or retrieve a field value from a variant data + property Field[const FieldName: RawUTF8]: Variant read GetFieldValue write SetFieldValue; + /// get a field value for a specified field + // - this method is faster than Field[], because it won't look for the field name + function GetFieldValue(aField: TSynTableFieldProperties): Variant; overload; + /// set a field value for a specified field + // - this method is faster than Field[], because it won't look for the field name + procedure SetFieldValue(aField: TSynTableFieldProperties; const Value: Variant); overload; + {$ifdef HASINLINE}inline;{$endif} + {$endif} + /// set a field value for a specified field, from SBF-encoded data + // - this method is faster than the other, because it won't look for the field + // name nor make any variant conversion + procedure SetFieldSBFValue(aField: TSynTableFieldProperties; const Value: TSBFString); + /// get a field value for a specified field, into SBF-encoded data + // - this method is faster than the other, because it won't look for the field + // name nor make any variant conversion + function GetFieldSBFValue(aField: TSynTableFieldProperties): TSBFString; + /// filter the SBF buffer record content with all registered filters + // - all field values are filtered in-place, following our SBF compact + // binary format encoding for this record + procedure FilterSBFValue; {$ifdef HASINLINE}inline;{$endif} + /// check the registered constraints according to a record SBF buffer + // - returns '' on success + // - returns an error message e.g. if a tftUnique constraint failed + // - RecordIndex=-1 in case of adding, or the physical index of the updated record + function ValidateSBFValue(RecordIndex: integer): string; + end; + {$A+} { packet object not allowed since Delphi 2009 :( } +{$endif DELPHI5OROLDER} + + PUpdateFieldEvent = ^TUpdateFieldEvent; + + /// an opaque structure used for TSynTable.UpdateFieldEvent method + TUpdateFieldEvent = record + /// the number of record added + Count: integer; + /// the list of IDs added + // - this list is already in increasing order, because GetIterating was + // called with the ioID order + IDs: TIntegerDynArray; + /// the offset of every record added + // - follows the IDs[] order + Offsets64: TInt64DynArray; + /// previous indexes: NewIndexs[oldIndex] := newIndex + NewIndexs: TIntegerDynArray; + /// the list of existing field in the previous data + AvailableFields: TSQLFieldBits; + /// where to write the updated data + WR: TFileBufferWriter; + end; + + /// will define a validation to be applied to a TSynTableFieldProperties field + // - a typical usage is to validate a value to be unique in the table + // (implemented in the TSynValidateTableUniqueField class) + // - the optional associated parameters are to be supplied JSON-encoded + // - ProcessField and ProcessRecordIndex properties will be filled before + // Process method call by TSynTableFieldProperties.Validate() + TSynValidateTable = class(TSynValidate) + protected + fProcessField: TSynTableFieldProperties; + fProcessRecordIndex: integer; + public + /// the associated TSQLRest instance + // - this value is filled by TSynTableFieldProperties.Validate with its + // self value to be used for the validation + // - it can be used in the overridden Process method + property ProcessField: TSynTableFieldProperties read fProcessField write fProcessField; + /// the associated record index (in case of update) + // - is set to -1 in case of adding, or the physical index of the updated record + // - this value is filled by TSynTableFieldProperties.Validate + // - it can be used in the overridden Process method + property ProcessRecordIndex: integer read fProcessRecordIndex write fProcessRecordIndex; + end; + + /// will define a validation for a TSynTableFieldProperties Unique field + // - implement constraints check e.g. if tfoUnique is set in Options + // - it will check that the field value is not void + // - it will check that the field value is not a duplicate + TSynValidateTableUniqueField = class(TSynValidateTable) + public + /// perform the unique field validation action to the specified value + // - duplication value check will use the ProcessField and + // ProcessRecordIndex properties, which will be filled before call by + // TSynTableFieldProperties.Validate() + // - aFieldIndex parameter is not used here, since we have already the + // ProcessField property set + // - here the Value is expected to be UTF-8 text, as converted from our SBF + // compact binary format via e.g. TSynTableFieldProperties.GetValue / + // GetRawUTF8: this is mandatory to have the validation rule fit with other + // TSynValidateTable classes + function Process(aFieldIndex: integer; const Value: RawUTF8; var ErrorMsg: string): boolean; override; + end; + + /// store the description of a table with records, to implement a Database + // - can be used with several storage engines, for instance TSynBigTableRecord + // - each record can have up to 64 fields + // - a mandatory ID field must be handled by the storage engine itself + // - will handle the storage of records into our SBF compact binary format, in + // which fixed-length fields are stored leftmost side, then variable-length + // fields follow + TSynTable = class + protected + fTableName: RawUTF8; + /// list of TSynTableFieldProperties instances + fField: TObjectList; + /// offset of the first variable length value field + fFieldVariableOffset: PtrUInt; + /// index of the first variable length value field + // - equals -1 if no variable length field exists + fFieldVariableIndex: integer; + /// bit is set for a tftWinAnsi, tftUTF8 or tftBlobInternal kind of field + // - these kind of field are encoded as a VarInt length, then the data + fFieldIsVarString: TSynTableFieldBits; + /// bit is set for a tftBlobExternal kind of field e.g. + fFieldIsExternal: TSynTableFieldBits; + /// event used for proper data retrieval of a given record buffer + fGetRecordData: TSynTableGetRecordData; + /// the global size of a default value, as encoded in our SBF compact binary format + fDefaultRecordLength: integer; + /// a default record data, as encoded in our SBF compact binary format + fDefaultRecordData: TSBFString; + /// list of TSynTableFieldProperties added via all AddField() call + fAddedField: TList; + /// true if any field has a tfoUnique option set + fFieldHasUniqueIndexes: boolean; + function GetFieldType(Index: integer): TSynTableFieldProperties; + function GetFieldCount: integer; + function GetFieldFromName(const aName: RawUTF8): TSynTableFieldProperties; + function GetFieldIndexFromName(const aName: RawUTF8): integer; + /// this method matchs the TSynTableFieldIndex event type + function GetFieldIndexFromShortName(const aName: ShortString): integer; + /// refresh Offset,FieldNumber,FieldSize and fFieldVariableIndex,fFieldVariableOffset + procedure AfterFieldModif; + public + /// create a table definition instance + constructor Create(const aTableName: RawUTF8); + /// create a table definition instance from a specified file reader + procedure LoadFrom(var RD: TFileBufferReader); + /// release used memory + destructor Destroy; override; + /// save field properties to a specified file writer + procedure SaveTo(WR: TFileBufferWriter); + + /// retrieve to the corresponding data address of a given field + function GetData(RecordBuffer: PUTF8Char; Field: TSynTableFieldProperties): pointer; + /// add a field description to the table + // - warning: the class responsible of the storage itself must process the + // data already stored when a field is created, e.g. in + // TSynBigTableRecord.AddFieldUpdate method + // - physical order does not necessary follow the AddField() call order: + // for better performance, it will try to store fixed-sized record first, + // multiple of 4 bytes first (access is faster if dat is 4 byte aligned), + // then variable-length after fixed-sized fields; in all case, a field + // indexed will be put first + function AddField(const aName: RawUTF8; aType: TSynTableFieldType; + aOptions: TSynTableFieldOptions=[]): TSynTableFieldProperties; + /// update a record content + // - return the updated record data, in our SBF compact binary format + // - if NewFieldData is not specified, a default 0 or '' value is appended + // - if NewFieldData is set, it must match the field value kind + // - warning: this method will update result in-place, so RecordBuffer MUST + // be <> pointer(result) or data corruption may occur + procedure UpdateFieldData(RecordBuffer: PUTF8Char; RecordBufferLen, + FieldIndex: integer; var result: TSBFString; const NewFieldData: TSBFString=''); + /// update a record content after any AddfieldUpdate, to refresh the data + // - AvailableFields must contain the list of existing fields in the previous data + function UpdateFieldRecord(RecordBuffer: PUTF8Char; var AvailableFields: TSQLFieldBits): TSBFString; + /// this Event is to be called for all data records (via a GetIterating method) + // after any AddfieldUpdate, to refresh the data + // - Opaque is in fact a pointer to a TUpdateFieldEvent record, and will contain + // all parameters set by TSynBigTableRecord.AddFieldUpdate, including a + // TFileBufferWriter instance to use to write the recreated data + // - it will work with either any newly added field, handly also field data + // order change in SBF record (e.g. when a fixed-sized field has been added + // on a record containing variable-length fields) + function UpdateFieldEvent(Sender: TObject; Opaque: pointer; ID, Index: integer; + Data: pointer; DataLen: integer): boolean; + /// event which must be called by the storage engine when some values are modified + // - if aOldIndex and aNewIndex are both >= 0, the corresponding aOldIndex + // will be replaced by aNewIndex value (i.e. called in case of a data Update) + // - if aOldIndex is -1 and aNewIndex is >= 0, aNewIndex refers to a just + // created item (i.e. called in case of a data Add) + // - if aOldIndex is >= 0 and aNewIndex is -1, aNewIndex refers to a just + // deleted item (i.e. called in case of a data Delete) + // - will update then sort all existing TSynTableFieldProperties.OrderedIndex + // values + // - the GetDataBuffer protected virtual method must have been overridden to + // properly return the record data for a given "physical/stored" index + // - aOldRecordData and aNewRecordData can be specified in order to guess + // if the field data has really been modified (speed up the update a lot + // to only sort indexed fields if its content has been really modified) + procedure FieldIndexModify(aOldIndex, aNewIndex: integer; + aOldRecordData, aNewRecordData: pointer); + /// return the total length of the given record buffer, encoded in our SBF + // compact binary format + function DataLength(RecordBuffer: pointer): integer; + {$ifndef NOVARIANTS} + /// create a Variant able to access any field content via late binding + // - i.e. you can use Var.Name to access the 'Name' field of record Var + // - if you leave ID and RecordBuffer void, a void record is created + function Data(aID: integer=0; RecordBuffer: pointer=nil; + RecordBufferLen: Integer=0): Variant; overload; + {$endif NOVARIANTS} + /// return a default content for ALL record fields + // - uses our SBF compact binary format + property DefaultRecordData: TSBFString read fDefaultRecordData; + /// list of TSynTableFieldProperties added via all AddField() call + // - this list will allow TSynBigTableRecord.AddFieldUpdate to refresh + // the data on disk according to the new field configuration + property AddedField: TList read fAddedField write fAddedField; + /// offset of the first variable length value field + property FieldVariableOffset: PtrUInt read fFieldVariableOffset; + public + {$ifndef DELPHI5OROLDER} + /// create a TJSONWriter, ready to be filled with GetJSONValues(W) below + // - will initialize all TJSONWriter.ColNames[] values according to the + // specified Fields index list, and initialize the JSON content + function CreateJSONWriter(JSON: TStream; Expand, withID: boolean; + const Fields: TSQLFieldIndexDynArray): TJSONWriter; overload; + /// create a TJSONWriter, ready to be filled with GetJSONValues(W) below + // - will initialize all TJSONWriter.ColNames[] values according to the + // specified Fields bit set, and initialize the JSON content + function CreateJSONWriter(JSON: TStream; Expand, withID: boolean; + const Fields: TSQLFieldBits): TJSONWriter; overload; + (** return the UTF-8 encoded JSON objects for the values contained + in the specified RecordBuffer encoded in our SBF compact binary format, + according to the Expand/WithID/Fields parameters of W + - if W.Expand is true, JSON data is an object, for direct use with any Ajax or .NET client: + ! {"col1":val11,"col2":"val12"} + - if W.Expand is false, JSON data is serialized (as used in TSQLTableJSON) + ! { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] } + - only fields with a bit set in W.Fields will be appended + - if W.WithID is true, then the first ID field value is included *) + procedure GetJSONValues(aID: integer; RecordBuffer: PUTF8Char; W: TJSONWriter); + /// can be used to retrieve all values matching a preparated TSynTableStatement + // - this method matchs the TSynBigTableIterateEvent callback definition + // - Sender will be the TSynBigTable instance, and Opaque will point to a + // TSynTableStatement instance (with all fields initialized, including Writer) + function IterateJSONValues(Sender: TObject; Opaque: pointer; ID: integer; + Data: pointer; DataLen: integer): boolean; + {$endif DELPHI5OROLDER} + /// check the registered constraints according to a record SBF buffer + // - returns '' on success + // - returns an error message e.g. if a tftUnique constraint failed + // - RecordIndex=-1 in case of adding, or the physical index of the updated record + function Validate(RecordBuffer: pointer; RecordIndex: integer): string; + /// filter the SBF buffer record content with all registered filters + // - all field values are filtered in-place, following our SBF compact + // binary format encoding for this record + procedure Filter(var RecordBuffer: TSBFString); + + /// event used for proper data retrieval of a given record buffer, according + // to the physical/storage index value (not per-ID index) + // - if not set, field indexes won't work + // - will be mapped e.g. to TSynBigTable.GetPointerFromPhysicalIndex + property GetRecordData: TSynTableGetRecordData read fGetRecordData write fGetRecordData; + public + /// the internal Table name used to identify it (e.g. from JSON or SQL) + // - similar to the SQL Table name + property TableName: RawUTF8 read fTableName write fTableName; + /// number of fields in this table + property FieldCount: integer read GetFieldCount; + /// retrieve the properties of a given field + // - returns nil if the specified Index is out of range + property Field[Index: integer]: TSynTableFieldProperties read GetFieldType; + /// retrieve the properties of a given field + // - returns nil if the specified Index is out of range + property FieldFromName[const aName: RawUTF8]: TSynTableFieldProperties read GetFieldFromName; default; + /// retrieve the index of a given field + // - returns -1 if the specified Index is out of range + property FieldIndexFromName[const aName: RawUTF8]: integer read GetFieldIndexFromName; + /// read-only access to the Field list + property FieldList: TObjectList read fField; + /// true if any field has a tfoUnique option set + property HasUniqueIndexes: boolean read fFieldHasUniqueIndexes; + end; + +{$ifndef NOVARIANTS} + /// a custom variant type used to have direct access to a record content + // - use TSynTable.Data method to retrieve such a Variant + // - this variant will store internaly a SBF compact binary format + // representation of the record content + // - uses internally a TSynTableData object + TSynTableVariantType = class(TSynInvokeableVariantType) + protected + procedure IntGet(var Dest: TVarData; const V: TVarData; Name: PAnsiChar); override; + procedure IntSet(const V, Value: TVarData; Name: PAnsiChar); override; + public + /// retrieve the SBF compact binary format representation of a record content + class function ToSBF(const V: Variant): TSBFString; + /// retrieve the ID value associated to a record content + class function ToID(const V: Variant): integer; + /// retrieve the TSynTable instance associated to a record content + class function ToTable(const V: Variant): TSynTable; + /// clear the content + procedure Clear(var V: TVarData); override; + /// copy two record content + procedure Copy(var Dest: TVarData; const Source: TVarData; + const Indirect: Boolean); override; + end; +{$endif NOVARIANTS} + +const + /// used by TSynTableStatement.WhereField for "SELECT .. FROM TableName WHERE ID=?" + SYNTABLESTATEMENTWHEREID = 0; + + +/// low-level integer comparison according to a specified operator +// - SBF must point to the values encoded in our SBF compact binary format +// - Value must contain the plain integer value +// - Value can be a Currency accessed via a PInt64 +// - will work only for tftBoolean, tftUInt8, tftUInt16, tftUInt24, +// tftInt32, tftInt64 and tftCurrency field types +// - will handle only soEqualTo...soGreaterThanOrEqualTo operators +// - if SBFEnd is not nil, it will test for all values until SBF>=SBFEnd +// (can be used for tftArray) +// - returns true if both values match, or false otherwise +function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char; + Value: Int64; Oper: TCompareOperator): boolean; overload; + +/// low-level floating-point comparison according to a specified operator +// - SBF must point to the values encoded in our SBF compact binary format +// - Value must contain the plain floating-point value +// - will work only for tftDouble field type +// - will handle only soEqualTo...soGreaterThanOrEqualTo operators +// - if SBFEnd is not nil, it will test for all values until SBF>=SBFEnd +// (can be used for tftArray) +// - returns true if both values match, or false otherwise +function CompareOperator(SBF, SBFEnd: PUTF8Char; + Value: double; Oper: TCompareOperator): boolean; overload; + +/// low-level text comparison according to a specified operator +// - SBF must point to the values encoded in our SBF compact binary format +// - Value must contain the plain text value, in the same encoding (either +// WinAnsi either UTF-8, as FieldType defined for the SBF value) +// - will work only for tftWinAnsi and tftUTF8 field types +// - will handle all kind of operators (including soBeginWith, soContains and +// soSoundsLike*) but soSoundsLike* won't make use of the CaseSensitive parameter +// - for soSoundsLikeEnglish, soSoundsLikeFrench and soSoundsLikeSpanish +// operators, Value is not a real PUTF8Char but a prepared PSynSoundEx +// - if SBFEnd is not nil, it will test for all values until SBF>=SBFEnd +// (can be used for tftArray) +// - returns true if both values match, or false otherwise +function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char; + Value: PUTF8Char; ValueLen: integer; Oper: TCompareOperator; + CaseSensitive: boolean): boolean; overload; + +/// convert any AnsiString content into our SBF compact binary format storage +procedure ToSBFStr(const Value: RawByteString; out Result: TSBFString); + + +{ ************ low-level buffer processing functions ************************* } + +type + /// implements a thread-safe Bloom Filter storage + // - a "Bloom Filter" is a space-efficient probabilistic data structure, + // that is used to test whether an element is a member of a set. False positive + // matches are possible, but false negatives are not. Elements can be added to + // the set, but not removed. Typical use cases are to avoid unecessary + // slow disk or network access if possible, when a lot of items are involved. + // - memory use is very low, when compared to storage of all values: fewer + // than 10 bits per element are required for a 1% false positive probability, + // independent of the size or number of elements in the set - for instance, + // storing 10,000,000 items presence with 1% of false positive ratio + // would consume only 11.5 MB of memory, using 7 hash functions + // - use Insert() methods to add an item to the internal bits array, and + // Reset() to clear all bits array, if needed + // - MayExist() function would check if the supplied item was probably set + // - SaveTo() and LoadFrom() methods allow transmission of the bits array, + // for a disk/database storage or transmission over a network + // - internally, several (hardware-accelerated) crc32c hash functions will be + // used, with some random seed values, to simulate several hashing functions + // - Insert/MayExist/Reset methods are thread-safe + TSynBloomFilter = class(TSynPersistentLock) + private + fSize: cardinal; + fFalsePositivePercent: double; + fBits: cardinal; + fHashFunctions: cardinal; + fInserted: cardinal; + fStore: RawByteString; + function GetInserted: cardinal; + public + /// initialize the internal bits storage for a given number of items + // - by default, internal bits array size will be guess from a 1 % false + // positive rate - but you may specify another value, to reduce memory use + // - this constructor would compute and initialize Bits and HashFunctions + // corresponding to the expected false positive ratio + constructor Create(aSize: integer; aFalsePositivePercent: double = 1); reintroduce; overload; + /// initialize the internal bits storage from a SaveTo() binary buffer + // - this constructor will initialize the internal bits array calling LoadFrom() + constructor Create(const aSaved: RawByteString; aMagic: cardinal=$B1003F11); reintroduce; overload; + /// add an item in the internal bits array storage + // - this method is thread-safe + procedure Insert(const aValue: RawByteString); overload; + /// add an item in the internal bits array storage + // - this method is thread-safe + procedure Insert(aValue: pointer; aValueLen: integer); overload; virtual; + /// clear the internal bits array storage + // - you may call this method after some time, if some items may have + // been removed, to reduce false positives + // - this method is thread-safe + procedure Reset; virtual; + /// returns TRUE if the supplied items was probably set via Insert() + // - some false positive may occur, but not much than FalsePositivePercent + // - this method is thread-safe + function MayExist(const aValue: RawByteString): boolean; overload; + /// returns TRUE if the supplied items was probably set via Insert() + // - some false positive may occur, but not much than FalsePositivePercent + // - this method is thread-safe + function MayExist(aValue: pointer; aValueLen: integer): boolean; overload; + /// store the internal bits array into a binary buffer + // - may be used to transmit or store the state of a dataset, avoiding + // to recompute all Insert() at program startup, or to synchronize + // networks nodes information and reduce the number of remote requests + function SaveTo(aMagic: cardinal=$B1003F11): RawByteString; overload; + /// store the internal bits array into a binary buffer + // - may be used to transmit or store the state of a dataset, avoiding + // to recompute all Insert() at program startup, or to synchronize + // networks nodes information and reduce the number of remote requests + procedure SaveTo(aDest: TFileBufferWriter; aMagic: cardinal=$B1003F11); overload; + /// read the internal bits array from a binary buffer + // - as previously serialized by the SaveTo method + // - may be used to transmit or store the state of a dataset + function LoadFrom(const aSaved: RawByteString; aMagic: cardinal=$B1003F11): boolean; overload; + /// read the internal bits array from a binary buffer + // - as previously serialized by the SaveTo method + // - may be used to transmit or store the state of a dataset + function LoadFrom(P: PByte; PLen: integer; aMagic: cardinal=$B1003F11): boolean; overload; virtual; + published + /// maximum number of items which are expected to be inserted + property Size: cardinal read fSize; + /// expected percentage (1..100) of false positive results for MayExists() + property FalsePositivePercent: double read fFalsePositivePercent; + /// number of bits stored in the internal bits array + property Bits: cardinal read fBits; + /// how many hash functions would be applied for each Insert() + property HashFunctions: cardinal read fHashFunctions; + /// how many times the Insert() method has been called + property Inserted: cardinal read GetInserted; + end; + + /// implements a thread-safe differential Bloom Filter storage + // - this inherited class is able to compute incremental serialization of + // its internal bits array, to reduce network use + // - an obfuscated revision counter is used to identify storage history + TSynBloomFilterDiff = class(TSynBloomFilter) + protected + fRevision: Int64; + fSnapShotAfterMinutes: cardinal; + fSnapshotAfterInsertCount: cardinal; + fSnapshotTimestamp: Int64; + fSnapshotInsertCount: cardinal; + fKnownRevision: Int64; + fKnownStore: RawByteString; + public + /// add an item in the internal bits array storage + // - this overloaded thread-safe method would compute fRevision + procedure Insert(aValue: pointer; aValueLen: integer); override; + /// clear the internal bits array storage + // - this overloaded thread-safe method would reset fRevision + procedure Reset; override; + /// store the internal bits array into an incremental binary buffer + // - here the difference from a previous SaveToDiff revision will be computed + // - if aKnownRevision is outdated (e.g. if equals 0), the whole bits array + // would be returned, and around 10 bits per item would be transmitted + // (for 1% false positive ratio) + // - incremental retrieval would then return around 10 bytes per newly added + // item since the last snapshot reference state (with 1% ratio, i.e. 7 hash + // functions) + function SaveToDiff(const aKnownRevision: Int64): RawByteString; + /// use the current internal bits array state as known revision + // - is done the first time SaveToDiff() is called, then after 1/32th of + // the filter size has been inserted (see SnapshotAfterInsertCount property), + // or after SnapShotAfterMinutes property timeout period + procedure DiffSnapshot; + /// retrieve the revision number from an incremental binary buffer + // - returns 0 if the supplied binary buffer does not match this bloom filter + function DiffKnownRevision(const aDiff: RawByteString): Int64; + /// read the internal bits array from an incremental binary buffer + // - as previously serialized by the SaveToDiff() method + // - may be used to transmit or store the state of a dataset + // - returns false if the supplied content is incorrect, e.g. if the known + // revision is deprecated + function LoadFromDiff(const aDiff: RawByteString): boolean; + /// the opaque revision number of this internal storage + // - is in fact the Unix timestamp shifted by 31 bits, and an incremental + // counter: this pattern will allow consistent IDs over several ServPanels + property Revision: Int64 read fRevision; + /// after how many Insert() the internal bits array storage should be + // promoted as known revision + // - equals Size div 32 by default + property SnapshotAfterInsertCount: cardinal read fSnapshotAfterInsertCount + write fSnapshotAfterInsertCount; + /// after how many time the internal bits array storage should be + // promoted as known revision + // - equals 30 minutes by default + property SnapShotAfterMinutes: cardinal read fSnapShotAfterMinutes + write fSnapShotAfterMinutes; + end; + + +/// RLE compression of a memory buffer containing mostly zeros +// - will store the number of consecutive zeros instead of plain zero bytes +// - used for spare bit sets, e.g. TSynBloomFilter serialization +// - will also compute the crc32c of the supplied content +// - use ZeroDecompress() to expand the compressed result +// - resulting content would be at most 14 bytes bigger than the input +// - you may use this function before SynLZ compression +procedure ZeroCompress(P: PAnsiChar; Len: integer; Dest: TFileBufferWriter); + +/// RLE uncompression of a memory buffer containing mostly zeros +// - returns Dest='' if P^ is not a valid ZeroCompress() function result +// - used for spare bit sets, e.g. TSynBloomFilter serialization +// - will also check the crc32c of the supplied content +procedure ZeroDecompress(P: PByte; Len: integer; {$ifdef FPC}var{$else}out{$endif} Dest: RawByteString); + +/// RLE compression of XORed memory buffers resulting in mostly zeros +// - will perform ZeroCompress(Dest^ := New^ xor Old^) without any temporary +// memory allocation +// - is used e.g. by TSynBloomFilterDiff.SaveToDiff() in incremental mode +// - will also compute the crc32c of the supplied content +procedure ZeroCompressXor(New,Old: PAnsiChar; Len: cardinal; Dest: TFileBufferWriter); + +/// RLE uncompression and ORing of a memory buffer containing mostly zeros +// - will perform Dest^ := Dest^ or ZeroDecompress(P^) without any temporary +// memory allocation +// - is used e.g. by TSynBloomFilterDiff.LoadFromDiff() in incremental mode +// - returns false if P^ is not a valid ZeroCompress/ZeroCompressXor() result +// - will also check the crc32c of the supplied content +function ZeroDecompressOr(P,Dest: PAnsiChar; Len,DestLen: integer): boolean; + + +const + /// normal pattern search depth for DeltaCompress() + // - gives good results on most content + DELTA_LEVEL_FAST = 100; + /// brutal pattern search depth for DeltaCompress() + // - may become very slow, with minor benefit, on huge content + DELTA_LEVEL_BEST = 500; + /// 2MB as internal chunks/window default size for DeltaCompress() + // - will use up to 9 MB of RAM during DeltaCompress() - none in DeltaExtract() + DELTA_BUF_DEFAULT = 2 shl 20; + +/// compute difference of two binary buffers +// - returns '=' for equal buffers, or an optimized binary delta +// - DeltaExtract() could be used later on to compute New from Old + Delta +function DeltaCompress(const New, Old: RawByteString; + Level: integer=DELTA_LEVEL_FAST; BufSize: integer=DELTA_BUF_DEFAULT): RawByteString; overload; + +/// compute difference of two binary buffers +// - returns '=' for equal buffers, or an optimized binary delta +// - DeltaExtract() could be used later on to compute New from Old +function DeltaCompress(New, Old: PAnsiChar; NewSize, OldSize: integer; + Level: integer=DELTA_LEVEL_FAST; BufSize: integer=DELTA_BUF_DEFAULT): RawByteString; overload; + +/// compute difference of two binary buffers +// - returns '=' for equal buffers, or an optimized binary delta +// - DeltaExtract() could be used later on to compute New from Old + Delta +// - caller should call Freemem(Delta) once finished with the output buffer +function DeltaCompress(New, Old: PAnsiChar; NewSize, OldSize: integer; + out Delta: PAnsiChar; Level: integer=DELTA_LEVEL_FAST; BufSize: integer=DELTA_BUF_DEFAULT): integer; overload; + +type + /// result of function DeltaExtract() + TDeltaError = ( + dsSuccess, dsCrcCopy, dsCrcComp, dsCrcBegin, dsCrcEnd, dsCrcExtract, dsFlag, dsLen); + +/// returns how many bytes a DeltaCompress() result will expand to +function DeltaExtractSize(const Delta: RawByteString): integer; overload; + +/// returns how many bytes a DeltaCompress() result will expand to +function DeltaExtractSize(Delta: PAnsiChar): integer; overload; + +/// apply the delta binary as computed by DeltaCompress() +// - decompression don't use any RAM, will perform crc32c check, and is very fast +// - return dsSuccess if was uncompressed to aOutUpd as expected +function DeltaExtract(const Delta,Old: RawByteString; out New: RawByteString): TDeltaError; overload; + +/// low-level apply the delta binary as computed by DeltaCompress() +// - New should already be allocated with DeltaExtractSize(Delta) bytes +// - as such, expect Delta, Old and New to be <> nil, and Delta <> '=' +// - return dsSuccess if was uncompressed to aOutUpd as expected +function DeltaExtract(Delta,Old,New: PAnsiChar): TDeltaError; overload; + +function ToText(err: TDeltaError): PShortString; overload; + + +type + /// safe decoding of a TFileBufferWriter content + // - similar to TFileBufferReader, but faster and only for in-memory buffer + // - is also safer, since will check for reaching end of buffer + // - raise a EFastReader exception on decoding error (e.g. if a buffer + // overflow may occur) or call OnErrorOverflow/OnErrorData event handlers + {$ifdef FPC_OR_UNICODE}TFastReader = record{$else}TFastReader = object{$endif} + public + /// the current position in the memory + P: PAnsiChar; + /// the last position in the buffer + Last: PAnsiChar; + /// use this event to customize the ErrorOverflow process + OnErrorOverflow: procedure of object; + /// use this event to customize the ErrorData process + OnErrorData: procedure(const fmt: RawUTF8; const args: array of const) of object; + /// some opaque value, which may be a version number to define the binary layout + Tag: PtrInt; + /// initialize the reader from a memory block + procedure Init(Buffer: pointer; Len: integer); overload; + /// initialize the reader from a RawByteString content + procedure Init(const Buffer: RawByteString); overload; + /// raise a EFastReader with an "overflow" error message + procedure ErrorOverflow; + /// raise a EFastReader with an "incorrect data" error message + procedure ErrorData(const fmt: RawUTF8; const args: array of const); + /// read the next 32-bit signed value from the buffer + function VarInt32: integer; {$ifdef HASINLINE}inline;{$endif} + /// read the next 32-bit unsigned value from the buffer + function VarUInt32: cardinal; + /// try to read the next 32-bit signed value from the buffer + // - don't change the current position + function PeekVarInt32(out value: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} + /// try to read the next 32-bit unsigned value from the buffer + // - don't change the current position + function PeekVarUInt32(out value: PtrUInt): boolean; + /// read the next 32-bit unsigned value from the buffer + // - this version won't call ErrorOverflow, but return false on error + // - returns true on read success + function VarUInt32Safe(out Value: cardinal): boolean; + /// read the next 64-bit signed value from the buffer + function VarInt64: Int64; {$ifdef HASINLINE}inline;{$endif} + /// read the next 64-bit unsigned value from the buffer + function VarUInt64: QWord; + /// read the next RawUTF8 value from the buffer + function VarUTF8: RawUTF8; overload; + /// read the next RawUTF8 value from the buffer + procedure VarUTF8(out result: RawUTF8); overload; + /// read the next RawUTF8 value from the buffer + // - this version won't call ErrorOverflow, but return false on error + // - returns true on read success + function VarUTF8Safe(out Value: RawUTF8): boolean; + /// read the next RawByteString value from the buffer + function VarString: RawByteString; {$ifdef HASINLINE}inline;{$endif} + /// read the next pointer and length value from the buffer + procedure VarBlob(out result: TValueResult); overload; {$ifdef HASINLINE}inline;{$endif} + /// read the next pointer and length value from the buffer + function VarBlob: TValueResult; overload; {$ifdef HASINLINE}inline;{$endif} + /// read the next ShortString value from the buffer + function VarShortString: shortstring; {$ifdef HASINLINE}inline;{$endif} + /// fast ignore the next VarUInt32/VarInt32/VarUInt64/VarInt64 value + // - don't raise any exception, so caller could check explicitly for any EOF + procedure VarNextInt; overload; {$ifdef HASINLINE}inline;{$endif} + /// fast ignore the next count VarUInt32/VarInt32/VarUInt64/VarInt64 values + // - don't raise any exception, so caller could check explicitly for any EOF + procedure VarNextInt(count: integer); overload; + /// read the next byte from the buffer + function NextByte: byte; {$ifdef HASINLINE}inline;{$endif} + /// read the next byte from the buffer, checking + function NextByteSafe(dest: pointer): boolean; {$ifdef HASINLINE}inline;{$endif} + /// read the next 4 bytes from the buffer as a 32-bit unsigned value + function Next4: cardinal; {$ifdef HASINLINE}inline;{$endif} + /// read the next 8 bytes from the buffer as a 64-bit unsigned value + function Next8: Qword; {$ifdef HASINLINE}inline;{$endif} + /// consumes the next byte from the buffer, if matches a given value + function NextByteEquals(Value: byte): boolean; {$ifdef HASINLINE}inline;{$endif} + /// returns the current position, and move ahead the specified bytes + function Next(DataLen: PtrInt): pointer; {$ifdef HASINLINE}inline;{$endif} + /// returns the current position, and move ahead the specified bytes + function NextSafe(out Data: Pointer; DataLen: PtrInt): boolean; {$ifdef HASINLINE}inline;{$endif} + {$ifndef NOVARIANTS} + /// read the next variant from the buffer + // - is a wrapper around VariantLoad(), so may suffer from buffer overflow + procedure NextVariant(var Value: variant; CustomVariantOptions: pointer); + /// read the JSON-serialized TDocVariant from the buffer + // - matches TFileBufferWriter.WriteDocVariantData format + procedure NextDocVariantData(out Value: variant; CustomVariantOptions: pointer); + {$endif NOVARIANTS} + /// copy data from the current position, and move ahead the specified bytes + procedure Copy(out Dest; DataLen: PtrInt); {$ifdef HASINLINE}inline;{$endif} + /// copy data from the current position, and move ahead the specified bytes + // - this version won't call ErrorOverflow, but return false on error + // - returns true on read success + function CopySafe(out Dest; DataLen: PtrInt): boolean; + /// apply TDynArray.LoadFrom on the buffer + // - will unserialize a previously appended dynamic array, e.g. as + // ! aWriter.WriteDynArray(DA); + procedure Read(var DA: TDynArray; NoCheckHash: boolean=false); + /// retrieved cardinal values encoded with TFileBufferWriter.WriteVarUInt32Array + // - only supports wkUInt32, wkVarInt32, wkVarUInt32 kind of encoding + function ReadVarUInt32Array(var Values: TIntegerDynArray): PtrInt; + /// retrieve some TAlgoCompress buffer, appended via Write() + // - BufferOffset could be set to reserve some bytes before the uncompressed buffer + function ReadCompressed(Load: TAlgoCompressLoad=aclNormal; BufferOffset: integer=0): RawByteString; + /// returns TRUE if the current position is the end of the input stream + function EOF: boolean; {$ifdef HASINLINE}inline;{$endif} + /// returns remaining length (difference between Last and P) + function RemainingLength: PtrUInt; {$ifdef HASINLINE}inline;{$endif} + end; + + /// abstract high-level handling of (SynLZ-)compressed persisted storage + // - LoadFromReader/SaveToWriter abstract methods should be overriden + // with proper binary persistence implementation + TSynPersistentStore = class(TSynPersistentLock) + protected + fName: RawUTF8; + fReader: TFastReader; + fReaderTemp: PRawByteString; + fLoadFromLastUncompressed, fSaveToLastUncompressed: integer; + fLoadFromLastAlgo: TAlgoCompress; + /// low-level virtual methods implementing the persistence reading + procedure LoadFromReader; virtual; + procedure SaveToWriter(aWriter: TFileBufferWriter); virtual; + public + /// initialize a void storage with the supplied name + constructor Create(const aName: RawUTF8); reintroduce; overload; virtual; + /// initialize a storage from a SaveTo persisted buffer + // - raise a EFastReader exception on decoding error + constructor CreateFrom(const aBuffer: RawByteString; + aLoad: TAlgoCompressLoad = aclNormal); + /// initialize a storage from a SaveTo persisted buffer + // - raise a EFastReader exception on decoding error + constructor CreateFromBuffer(aBuffer: pointer; aBufferLen: integer; + aLoad: TAlgoCompressLoad = aclNormal); + /// initialize a storage from a SaveTo persisted buffer + // - raise a EFastReader exception on decoding error + constructor CreateFromFile(const aFileName: TFileName; + aLoad: TAlgoCompressLoad = aclNormal); + /// fill the storage from a SaveTo persisted buffer + // - actually call the LoadFromReader() virtual method for persistence + // - raise a EFastReader exception on decoding error + procedure LoadFrom(const aBuffer: RawByteString; + aLoad: TAlgoCompressLoad = aclNormal); overload; + /// initialize the storage from a SaveTo persisted buffer + // - actually call the LoadFromReader() virtual method for persistence + // - raise a EFastReader exception on decoding error + procedure LoadFrom(aBuffer: pointer; aBufferLen: integer; + aLoad: TAlgoCompressLoad = aclNormal); overload; virtual; + /// initialize the storage from a SaveToFile content + // - actually call the LoadFromReader() virtual method for persistence + // - returns false if the file is not found, true if the file was loaded + // without any problem, or raise a EFastReader exception on decoding error + function LoadFromFile(const aFileName: TFileName; + aLoad: TAlgoCompressLoad = aclNormal): boolean; + /// persist the content as a SynLZ-compressed binary blob + // - to be retrieved later on via LoadFrom method + // - actually call the SaveToWriter() protected virtual method for persistence + // - you can specify ForcedAlgo if you want to override the default AlgoSynLZ + // - BufferOffset could be set to reserve some bytes before the compressed buffer + procedure SaveTo(out aBuffer: RawByteString; nocompression: boolean=false; + BufLen: integer=65536; ForcedAlgo: TAlgoCompress=nil; BufferOffset: integer=0); overload; virtual; + /// persist the content as a SynLZ-compressed binary blob + // - just an overloaded wrapper + function SaveTo(nocompression: boolean=false; BufLen: integer=65536; + ForcedAlgo: TAlgoCompress=nil; BufferOffset: integer=0): RawByteString; overload; + {$ifdef HASINLINE}inline;{$endif} + /// persist the content as a SynLZ-compressed binary file + // - to be retrieved later on via LoadFromFile method + // - returns the number of bytes of the resulting file + // - actually call the SaveTo method for persistence + function SaveToFile(const aFileName: TFileName; nocompression: boolean=false; + BufLen: integer=65536; ForcedAlgo: TAlgoCompress=nil): PtrUInt; + /// one optional text associated with this storage + // - you can define this field as published to serialize its value in log/JSON + property Name: RawUTF8 read fName; + /// after a LoadFrom(), contains the uncompressed data size read + property LoadFromLastUncompressed: integer read fLoadFromLastUncompressed; + /// after a SaveTo(), contains the uncompressed data size written + property SaveToLastUncompressed: integer read fSaveToLastUncompressed; + end; + + /// implement binary persistence and JSON serialization (not deserialization) + TSynPersistentStoreJson = class(TSynPersistentStore) + protected + // append "name" -> inherited should add properties to the JSON object + procedure AddJSON(W: TTextWriter); virtual; + public + /// serialize this instance as a JSON object + function SaveToJSON(reformat: TTextWriterJSONFormat = jsonCompact): RawUTF8; + end; + + +type + /// item as stored in a TRawByteStringGroup instance + TRawByteStringGroupValue = record + Position: integer; + Value: RawByteString; + end; + PRawByteStringGroupValue = ^TRawByteStringGroupValue; + /// items as stored in a TRawByteStringGroup instance + TRawByteStringGroupValueDynArray = array of TRawByteStringGroupValue; + + /// store several RawByteString content with optional concatenation + {$ifdef UNICODE}TRawByteStringGroup = record{$else}TRawByteStringGroup = object{$endif} + public + /// actual list storing the data + Values: TRawByteStringGroupValueDynArray; + /// how many items are currently stored in Values[] + Count: integer; + /// the current size of data stored in Values[] + Position: integer; + /// naive but efficient cache for Find() + LastFind: integer; + /// add a new item to Values[] + procedure Add(const aItem: RawByteString); overload; + /// add a new item to Values[] + procedure Add(aItem: pointer; aItemLen: integer); overload; + {$ifndef DELPHI5OROLDER} + /// add another TRawByteStringGroup to Values[] + procedure Add(const aAnother: TRawByteStringGroup); overload; + /// low-level method to abort the latest Add() call + // - warning: will work only once, if an Add() has actually been just called: + // otherwise, the behavior is unexpected, and may wrongly truncate data + procedure RemoveLastAdd; + /// compare two TRawByteStringGroup instance stored text + function Equals(const aAnother: TRawByteStringGroup): boolean; + {$endif DELPHI5OROLDER} + /// clear any stored information + procedure Clear; + /// append stored information into another RawByteString, and clear content + procedure AppendTextAndClear(var aDest: RawByteString); + // compact the Values[] array into a single item + // - is also used by AsText to compute a single RawByteString + procedure Compact; + /// return all content as a single RawByteString + // - will also compact the Values[] array into a single item (which is returned) + function AsText: RawByteString; + /// return all content as a single TByteDynArray + function AsBytes: TByteDynArray; + /// save all content into a TTextWriter instance + procedure Write(W: TTextWriter; Escape: TTextWriterKind=twJSONEscape); overload; + /// save all content into a TFileBufferWriter instance + procedure WriteBinary(W: TFileBufferWriter); overload; + /// save all content as a string into a TFileBufferWriter instance + // - storing the length as WriteVarUInt32() prefix + procedure WriteString(W: TFileBufferWriter); + /// add another TRawByteStringGroup previously serialized via WriteString() + procedure AddFromReader(var aReader: TFastReader); + /// returns a pointer to Values[] containing a given position + // - returns nil if not found + function Find(aPosition: integer): PRawByteStringGroupValue; overload; + /// returns a pointer to Values[].Value containing a given position and length + // - returns nil if not found + function Find(aPosition, aLength: integer): pointer; overload; + /// returns the text at a given position in Values[] + // - text should be in a single Values[] entry + procedure FindAsText(aPosition, aLength: integer; out aText: RawByteString); overload; + {$ifdef HASINLINE}inline;{$endif} + /// returns the text at a given position in Values[] + // - text should be in a single Values[] entry + function FindAsText(aPosition, aLength: integer): RawByteString; overload; + {$ifdef HASINLINE}inline;{$endif} + {$ifndef NOVARIANTS} + /// returns the text at a given position in Values[] + // - text should be in a single Values[] entry + // - explicitly returns null if the supplied text was not found + procedure FindAsVariant(aPosition, aLength: integer; out aDest: variant); + {$ifdef HASINLINE}inline;{$endif} + {$endif} + /// append the text at a given position in Values[], JSON escaped by default + // - text should be in a single Values[] entry + procedure FindWrite(aPosition, aLength: integer; W: TTextWriter; + Escape: TTextWriterKind=twJSONEscape; TrailingCharsToIgnore: integer=0); + {$ifdef HASINLINE}inline;{$endif} + /// append the blob at a given position in Values[], base-64 encoded + // - text should be in a single Values[] entry + procedure FindWriteBase64(aPosition, aLength: integer; W: TTextWriter; + withMagic: boolean); + {$ifdef HASINLINE}inline;{$endif} + /// copy the text at a given position in Values[] + // - text should be in a single Values[] entry + procedure FindMove(aPosition, aLength: integer; aDest: pointer); + end; + /// pointer reference to a TRawByteStringGroup + PRawByteStringGroup = ^TRawByteStringGroup; + + +{ ************ Security and Identifier classes ************************** } + +type + /// 64-bit integer unique identifier, as computed by TSynUniqueIdentifierGenerator + // - they are increasing over time (so are much easier to store/shard/balance + // than UUID/GUID), and contain generation time and a 16-bit process ID + // - mapped by TSynUniqueIdentifierBits memory structure + // - may be used on client side for something similar to a MongoDB ObjectID, + // but compatible with TSQLRecord.ID: TID properties + TSynUniqueIdentifier = type Int64; + + /// 16-bit unique process identifier, used to compute TSynUniqueIdentifier + // - each TSynUniqueIdentifierGenerator instance is expected to have + // its own unique process identifier, stored as a 16 bit integer 1..65535 value + TSynUniqueIdentifierProcess = type word; + + {$A-} + /// map 64-bit integer unique identifier internal memory structure + // - as stored in TSynUniqueIdentifier = Int64 values, and computed by + // TSynUniqueIdentifierGenerator + // - bits 0..14 map a 15-bit increasing counter (collision-free) + // - bits 15..30 map a 16-bit process identifier + // - bits 31..63 map a 33-bit UTC time, encoded as seconds since Unix epoch + {$ifdef FPC_OR_UNICODE}TSynUniqueIdentifierBits = record{$else}TSynUniqueIdentifierBits = object{$endif} + public + /// the actual 64-bit storage value + // - in practice, only first 63 bits are used + Value: TSynUniqueIdentifier; + /// 15-bit counter (0..32767), starting with a random value + function Counter: word; + {$ifdef HASINLINE}inline;{$endif} + /// 16-bit unique process identifier + // - as specified to TSynUniqueIdentifierGenerator constructor + function ProcessID: TSynUniqueIdentifierProcess; + {$ifdef HASINLINE}inline;{$endif} + /// low-endian 4-byte value representing the seconds since the Unix epoch + // - time is expressed in Coordinated Universal Time (UTC), not local time + // - it uses in fact a 33-bit resolution, so is "Year 2038" bug-free + function CreateTimeUnix: TUnixTime; + {$ifdef HASINLINE}inline;{$endif} + /// fill this unique identifier structure from its TSynUniqueIdentifier value + // - is just a wrapper around PInt64(@self)^ + procedure From(const AID: TSynUniqueIdentifier); + {$ifdef HASINLINE}inline;{$endif} + {$ifndef NOVARIANTS} + /// convert this identifier as an explicit TDocVariant JSON object + // - returns e.g. + // ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1, + // ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"} + function AsVariant: variant; {$ifdef HASINLINE}inline;{$endif} + /// convert this identifier to an explicit TDocVariant JSON object + // - returns e.g. + // ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1, + // ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"} + procedure ToVariant(out result: variant); + {$endif NOVARIANTS} + /// extract the UTC generation timestamp from the identifier as TDateTime + // - time is expressed in Coordinated Universal Time (UTC), not local time + function CreateDateTime: TDateTime; + {$ifdef HASINLINE}inline;{$endif} + /// extract the UTC generation timestamp from the identifier + // - time is expressed in Coordinated Universal Time (UTC), not local time + function CreateTimeLog: TTimeLog; + {$ifndef DELPHI5OROLDER} + /// compare two Identifiers + function Equal(const Another: TSynUniqueIdentifierBits): boolean; + {$ifdef HASINLINE}inline;{$endif} + {$endif DELPHI5OROLDER} + /// convert the identifier into a 16 chars hexadecimal string + function ToHexa: RawUTF8; + {$ifdef HASINLINE}inline;{$endif} + /// fill this unique identifier back from a 16 chars hexadecimal string + // - returns TRUE if the supplied hexadecimal is on the expected format + // - returns FALSE if the supplied text is invalid + function FromHexa(const hexa: RawUTF8): boolean; + /// fill this unique identifier with a fake value corresponding to a given + // timestamp + // - may be used e.g. to limit database queries on a particular time range + // - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0 + procedure FromDateTime(const aDateTime: TDateTime); + /// fill this unique identifier with a fake value corresponding to a given + // timestamp + // - may be used e.g. to limit database queries on a particular time range + // - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0 + procedure FromUnixTime(const aUnixTime: TUnixTime); + end; + {$A+} + + /// points to a 64-bit integer identifier, as computed by TSynUniqueIdentifierGenerator + // - may be used to access the identifier internals, from its stored + // Int64 or TSynUniqueIdentifier value + PSynUniqueIdentifierBits = ^TSynUniqueIdentifierBits; + + /// a 24 chars cyphered hexadecimal string, mapping a TSynUniqueIdentifier + // - has handled by TSynUniqueIdentifierGenerator.ToObfuscated/FromObfuscated + TSynUniqueIdentifierObfuscated = type RawUTF8; + + /// thread-safe 64-bit integer unique identifier computation + // - may be used on client side for something similar to a MongoDB ObjectID, + // but compatible with TSQLRecord.ID: TID properties, since it will contain + // a 63-bit unsigned integer, following our ORM expectations + // - each identifier would contain a 16-bit process identifier, which is + // supplied by the application, and should be unique for this process at a + // given time + // - identifiers may be obfuscated as hexadecimal text, using both encryption + // and digital signature + TSynUniqueIdentifierGenerator = class(TSynPersistent) + protected + fUnixCreateTime: cardinal; + fLatestCounterOverflowUnixCreateTime: cardinal; + fIdentifier: TSynUniqueIdentifierProcess; + fIdentifierShifted: cardinal; + fLastCounter: cardinal; + fCrypto: array[0..7] of cardinal; // only fCrypto[6..7] are used in practice + fCryptoCRC: cardinal; + fSafe: TSynLocker; + function GetComputedCount: Int64; + public + /// initialize the generator for the given 16-bit process identifier + // - you can supply an obfuscation key, which should be shared for the + // whole system, so that you may use FromObfuscated/ToObfuscated methods + constructor Create(aIdentifier: TSynUniqueIdentifierProcess; + const aSharedObfuscationKey: RawUTF8=''); reintroduce; + /// finalize the generator structure + destructor Destroy; override; + /// return a new unique ID + // - this method is very optimized, and would use very little CPU + procedure ComputeNew(out result: TSynUniqueIdentifierBits); overload; + /// return a new unique ID, type-casted to an Int64 + function ComputeNew: Int64; overload; + {$ifdef HASINLINE}inline;{$endif} + /// return an unique ID matching this generator pattern, at a given timestamp + // - may be used e.g. to limit database queries on a particular time range + procedure ComputeFromDateTime(const aDateTime: TDateTime; out result: TSynUniqueIdentifierBits); + /// return an unique ID matching this generator pattern, at a given timestamp + // - may be used e.g. to limit database queries on a particular time range + procedure ComputeFromUnixTime(const aUnixTime: TUnixTime; out result: TSynUniqueIdentifierBits); + /// map a TSynUniqueIdentifier as 24 chars cyphered hexadecimal text + // - cyphering includes simple key-based encryption and a CRC-32 digital signature + function ToObfuscated(const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated; + /// retrieve a TSynUniqueIdentifier from 24 chars cyphered hexadecimal text + // - any file extension (e.g. '.jpeg') would be first deleted from the + // supplied obfuscated text + // - returns true if the supplied obfuscated text has the expected layout + // and a valid digital signature + // - returns false if the supplied obfuscated text is invalid + function FromObfuscated(const aObfuscated: TSynUniqueIdentifierObfuscated; + out aIdentifier: TSynUniqueIdentifier): boolean; + /// some 32-bit value, derivated from aSharedObfuscationKey as supplied + // to the class constructor + // - FromObfuscated and ToObfuscated methods will validate their hexadecimal + // content with this value to secure the associated CRC + // - may be used e.g. as system-depending salt + property CryptoCRC: cardinal read fCryptoCRC; + /// direct access to the associated mutex + property Safe: TSynLocker read fSafe; + published + /// the process identifier, associated with this generator + property Identifier: TSynUniqueIdentifierProcess read fIdentifier; + /// how many times ComputeNew method has been called + property ComputedCount: Int64 read GetComputedCount; + end; + +type + /// abstract TSynPersistent class allowing safe storage of a password + // - the associated Password, e.g. for storage or transmission encryption + // will be persisted encrypted with a private key (which can be customized) + // - if default simple symmetric encryption is not enough, you may define + // a custom TSynPersistentWithPasswordUserCrypt callback, e.g. to + // SynCrypto's CryptDataForCurrentUser, for hardened password storage + // - a published property should be defined as such in inherited class: + // ! property PasswordPropertyName: RawUTF8 read fPassword write fPassword; + // - use the PassWordPlain property to access to its uncyphered value + TSynPersistentWithPassword = class(TSynPersistent) + protected + fPassWord: RawUTF8; + fKey: cardinal; + function GetKey: cardinal; + {$ifdef HASINLINE}inline;{$endif} + function GetPassWordPlain: RawUTF8; + function GetPassWordPlainInternal(AppSecret: RawUTF8): RawUTF8; + procedure SetPassWordPlain(const Value: RawUTF8); + public + /// finalize the instance + destructor Destroy; override; + /// this class method could be used to compute the encrypted password, + // ready to be stored as JSON, according to a given private key + class function ComputePassword(const PlainPassword: RawUTF8; + CustomKey: cardinal=0): RawUTF8; overload; + /// this class method could be used to compute the encrypted password from + // a binary digest, ready to be stored as JSON, according to a given private key + // - just a wrapper around ComputePassword(BinToBase64URI()) + class function ComputePassword(PlainPassword: pointer; PlainPasswordLen: integer; + CustomKey: cardinal=0): RawUTF8; overload; + /// this class method could be used to decrypt a password, stored as JSON, + // according to a given private key + // - may trigger a ESynException if the password was stored using a custom + // TSynPersistentWithPasswordUserCrypt callback, and the current user + // doesn't match the expected user stored in the field + class function ComputePlainPassword(const CypheredPassword: RawUTF8; + CustomKey: cardinal=0; const AppSecret: RawUTF8=''): RawUTF8; + /// low-level function used to identify if a given field is a Password + // - this method is used e.g. by TJSONSerializer.WriteObject to identify the + // password field, since its published name is set by the inherited classes + function GetPasswordFieldAddress: pointer; + {$ifdef HASINLINE}inline;{$endif} + /// the private key used to cypher the password storage on serialization + // - application can override the default 0 value at runtime + property Key: cardinal read GetKey write fKey; + /// access to the associated unencrypted Password value + // - read may trigger a ESynException if the password was stored using a + // custom TSynPersistentWithPasswordUserCrypt callback, and the current user + // doesn't match the expected user stored in the field + property PasswordPlain: RawUTF8 read GetPassWordPlain write SetPassWordPlain; + end; + +var + /// function prototype to customize TSynPersistent class password storage + // - is called when 'user1:base64pass1,user2:base64pass2' layout is found, + // and the current user logged on the system is user1 or user2 + // - you should not call this low-level method, but assign e.g. from SynCrypto: + // $ TSynPersistentWithPasswordUserCrypt := CryptDataForCurrentUser; + TSynPersistentWithPasswordUserCrypt: + function(const Data,AppServer: RawByteString; Encrypt: boolean): RawByteString; + +type + /// could be used to store a credential pair, as user name and password + // - password will be stored with TSynPersistentWithPassword encryption + TSynUserPassword = class(TSynPersistentWithPassword) + protected + fUserName: RawUTF8; + published + /// the associated user name + property UserName: RawUTF8 read FUserName write FUserName; + /// the associated encrypted password + // - use the PasswordPlain public property to access to the uncrypted password + property Password: RawUTF8 read FPassword write FPassword; + end; + + /// handle safe storage of any connection properties + // - would be used by SynDB.pas to serialize TSQLDBConnectionProperties, or + // by mORMot.pas to serialize TSQLRest instances + // - the password will be stored as Base64, after a simple encryption as + // defined by TSynPersistentWithPassword + // - typical content could be: + // $ { + // $ "Kind": "TSQLDBSQLite3ConnectionProperties", + // $ "ServerName": "server", + // $ "DatabaseName": "", + // $ "User": "", + // $ "Password": "PtvlPA==" + // $ } + // - the "Kind" value will be used to let the corresponding TSQLRest or + // TSQLDBConnectionProperties NewInstance*() class methods create the + // actual instance, from its class name + TSynConnectionDefinition = class(TSynPersistentWithPassword) + protected + fKind: string; + fServerName: RawUTF8; + fDatabaseName: RawUTF8; + fUser: RawUTF8; + public + /// unserialize the database definition from JSON + // - as previously serialized with the SaveToJSON method + // - you can specify a custom Key used for password encryption, if the + // default value is not safe enough for you + // - this method won't use JSONToObject() so avoid any dependency to mORMot.pas + constructor CreateFromJSON(const JSON: RawUTF8; Key: cardinal=0); virtual; + /// serialize the database definition as JSON + // - this method won't use ObjectToJSON() so avoid any dependency to mORMot.pas + function SaveToJSON: RawUTF8; virtual; + published + /// the class name implementing the connection or TSQLRest instance + // - will be used to instantiate the expected class type + property Kind: string read fKind write fKind; + /// the associated server name (or file, for SQLite3) to be connected to + property ServerName: RawUTF8 read fServerName write fServerName; + /// the associated database name (if any), or additional options + property DatabaseName: RawUTF8 read fDatabaseName write fDatabaseName; + /// the associated User Identifier (if any) + property User: RawUTF8 read fUser write fUser; + /// the associated Password, e.g. for storage or transmission encryption + // - will be persisted encrypted with a private key + // - use the PassWordPlain property to access to its uncyphered value + property Password: RawUTF8 read fPassword write fPassword; + end; + + +type + /// class-reference type (metaclass) of an authentication class + TSynAuthenticationClass = class of TSynAuthenticationAbstract; + + /// abstract authentication class, implementing safe token/challenge security + // and a list of active sessions + // - do not use this class, but plain TSynAuthentication + TSynAuthenticationAbstract = class + protected + fSessions: TIntegerDynArray; + fSessionsCount: Integer; + fSessionGenerator: integer; + fTokenSeed: Int64; + fSafe: TSynLocker; + function ComputeCredential(previous: boolean; const UserName,PassWord: RawUTF8): cardinal; virtual; + function GetPassword(const UserName: RawUTF8; out Password: RawUTF8): boolean; virtual; abstract; + function GetUsersCount: integer; virtual; abstract; + public + /// initialize the authentication scheme + constructor Create; + /// finalize the authentation + destructor Destroy; override; + /// register one credential for a given user + // - this abstract method will raise an exception: inherited classes should + // implement them as expected + procedure AuthenticateUser(const aName, aPassword: RawUTF8); virtual; + /// unregister one credential for a given user + // - this abstract method will raise an exception: inherited classes should + // implement them as expected + procedure DisauthenticateUser(const aName: RawUTF8); virtual; + /// create a new session + // - should return 0 on authentication error, or an integer session ID + // - this method will check the User name and password, and create a new session + function CreateSession(const User: RawUTF8; Hash: cardinal): integer; virtual; + /// check if the session exists in the internal list + function SessionExists(aID: integer): boolean; + /// delete a session + procedure RemoveSession(aID: integer); + /// returns the current identification token + // - to be sent to the client for its authentication challenge + function CurrentToken: Int64; + /// the number of current opened sessions + property SessionsCount: integer read fSessionsCount; + /// the number of registered users + property UsersCount: integer read GetUsersCount; + /// to be used to compute a Hash on the client sude, for a given Token + // - the token should have been retrieved from the server, and the client + // should compute and return this hash value, to perform the authentication + // challenge and create the session + // - internal algorithm is not cryptographic secure, but fast and safe + class function ComputeHash(Token: Int64; const UserName,PassWord: RawUTF8): cardinal; virtual; + end; + + /// simple authentication class, implementing safe token/challenge security + // - maintain a list of user / name credential pairs, and a list of sessions + // - is not meant to handle authorization, just plain user access validation + // - used e.g. by TSQLDBConnection.RemoteProcessMessage (on server side) and + // TSQLDBProxyConnectionPropertiesAbstract (on client side) in SynDB.pas + TSynAuthentication = class(TSynAuthenticationAbstract) + protected + fCredentials: TSynNameValue; // store user/password pairs + function GetPassword(const UserName: RawUTF8; out Password: RawUTF8): boolean; override; + function GetUsersCount: integer; override; + public + /// initialize the authentication scheme + // - you can optionally register one user credential + constructor Create(const aUserName: RawUTF8=''; const aPassword: RawUTF8=''); reintroduce; + /// register one credential for a given user + procedure AuthenticateUser(const aName, aPassword: RawUTF8); override; + /// unregister one credential for a given user + procedure DisauthenticateUser(const aName: RawUTF8); override; + end; + + +{ ************ Expression Search Engine ************************** } + +type + /// exception type used by TExprParser + EExprParser = class(ESynException); + + /// identify an expression search engine node type, as used by TExprParser + TExprNodeType = (entWord, entNot, entOr, entAnd); + + /// results returned by TExprParserAbstract.Parse method + TExprParserResult = ( + eprSuccess, eprNoExpression, + eprMissingParenthesis, eprTooManyParenthesis, eprMissingFinalWord, + eprInvalidExpression, eprUnknownVariable, eprUnsupportedOperator, + eprInvalidConstantOrVariable); + + TParserAbstract = class; + + /// stores an expression search engine node, as used by TExprParser + TExprNode = class(TSynPersistent) + protected + fNext: TExprNode; + fNodeType: TExprNodeType; + function Append(node: TExprNode): boolean; + public + /// initialize a node for the search engine + constructor Create(nodeType: TExprNodeType); reintroduce; + /// recursively destroys the linked list of nodes (i.e. Next) + destructor Destroy; override; + /// browse all nodes until Next = nil + function Last: TExprNode; + /// points to the next node in the parsed tree + property Next: TExprNode read fNext; + /// what is actually stored in this node + property NodeType: TExprNodeType read fNodeType; + end; + + /// abstract class to handle word search, as used by TExprParser + TExprNodeWordAbstract = class(TExprNode) + protected + fOwner: TParserAbstract; + fWord: RawUTF8; + /// should be set from actual data before TExprParser.Found is called + fFound: boolean; + function ParseWord: TExprParserResult; virtual; abstract; + public + /// you should override this virtual constructor for proper initialization + constructor Create(aOwner: TParserAbstract; const aWord: RawUTF8); reintroduce; virtual; + end; + + /// class-reference type (metaclass) for a TExprNode + // - allow to customize the actual searching process for entWord + TExprNodeWordClass = class of TExprNodeWordAbstract; + + /// parent class of TExprParserAbstract + TParserAbstract = class(TSynPersistent) + protected + fExpression, fCurrentWord, fAndWord, fOrWord, fNotWord: RawUTF8; + fCurrent: PUTF8Char; + fCurrentError: TExprParserResult; + fFirstNode: TExprNode; + fWordClass: TExprNodeWordClass; + fWords: array of TExprNodeWordAbstract; + fWordCount: integer; + fNoWordIsAnd: boolean; + fFoundStack: array[byte] of boolean; // simple stack-based virtual machine + procedure ParseNextCurrentWord; virtual; abstract; + function ParseExpr: TExprNode; + function ParseFactor: TExprNode; + function ParseTerm: TExprNode; + procedure Clear; virtual; + // override this method to initialize fWordClass and fAnd/Or/NotWord + procedure Initialize; virtual; abstract; + /// perform the expression search over TExprNodeWord.fFound flags + // - warning: caller should check that fFirstNode<>nil (e.g. WordCount>0) + function Execute: boolean; {$ifdef HASINLINE}inline;{$endif} + public + /// initialize an expression parser + constructor Create; override; + /// finalize the expression parser + destructor Destroy; override; + /// initialize the parser from a given text expression + function Parse(const aExpression: RawUTF8): TExprParserResult; + /// try this parser class on a given text expression + // - returns '' on success, or an explicit error message (e.g. + // 'Missing parenthesis') + class function ParseError(const aExpression: RawUTF8): RawUTF8; + /// the associated text expression used to define the search + property Expression: RawUTF8 read fExpression; + /// how many words did appear in the search expression + property WordCount: integer read fWordCount; + end; + + /// abstract class to parse a text expression into nodes + // - you should inherit this class to provide actual text search + // - searched expressions can use parenthesis and &=AND -=WITHOUT +=OR operators, + // e.g. '((w1 & w2) - w3) + w4' means ((w1 and w2) without w3) or w4 + // - no operator is handled like a AND, e.g. 'w1 w2' = 'w1 & w2' + TExprParserAbstract = class(TParserAbstract) + protected + procedure ParseNextCurrentWord; override; + // may be overriden to provide custom words escaping (e.g. handle quotes) + procedure ParseNextWord; virtual; + procedure Initialize; override; + end; + + /// search expression engine using TMatch for the actual word searches + TExprParserMatch = class(TExprParserAbstract) + protected + fCaseSensitive: boolean; + fMatchedLastSet: integer; + procedure Initialize; override; + public + /// initialize the search engine + constructor Create(aCaseSensitive: boolean = true); reintroduce; + /// returns TRUE if the expression is within the text buffer + function Search(aText: PUTF8Char; aTextLen: PtrInt): boolean; overload; + /// returns TRUE if the expression is within the text buffer + function Search(const aText: RawUTF8): boolean; overload; {$ifdef HASINLINE}inline;{$endif} + end; + +const + /// may be used when overriding TExprParserAbstract.ParseNextWord method + PARSER_STOPCHAR = ['&', '+', '-', '(', ')']; + +function ToText(r: TExprParserResult): PShortString; overload; +function ToUTF8(r: TExprParserResult): RawUTF8; overload; + + +{ ************ Multi-Threading classes ************************** } + +type + /// internal item definition, used by TPendingTaskList storage + TPendingTaskListItem = packed record + /// the task should be executed when TPendingTaskList.GetTimestamp reaches + // this value + Timestamp: Int64; + /// the associated task, stored by representation as raw binary + Task: RawByteString; + end; + /// internal list definition, used by TPendingTaskList storage + TPendingTaskListItemDynArray = array of TPendingTaskListItem; + + /// handle a list of tasks, stored as RawByteString, with a time stamp + // - internal time stamps would be GetTickCount64 by default, so have a + // resolution of about 16 ms under Windows + // - you can add tasks to the internal list, to be executed after a given + // delay, using a post/peek like algorithm + // - execution delays are not expected to be accurate, but are best guess, + // according to NextTask call + // - this implementation is thread-safe, thanks to the Safe internal locker + TPendingTaskList = class(TSynPersistentLock) + protected + fCount: Integer; + fTask: TPendingTaskListItemDynArray; + fTasks: TDynArray; + function GetCount: integer; + function GetTimestamp: Int64; virtual; + public + /// initialize the list memory and resources + constructor Create; override; + /// append a task, specifying a delay in milliseconds from current time + procedure AddTask(aMilliSecondsDelayFromNow: integer; const aTask: RawByteString); virtual; + /// append several tasks, specifying a delay in milliseconds between tasks + // - first supplied delay would be computed from the current time, then + // it would specify how much time to wait between the next supplied task + procedure AddTasks(const aMilliSecondsDelays: array of integer; + const aTasks: array of RawByteString); + /// retrieve the next pending task + // - returns '' if there is no scheduled task available at the current time + // - returns the next stack as defined corresponding to its specified delay + function NextPendingTask: RawByteString; virtual; + /// flush all pending tasks + procedure Clear; virtual; + /// access to the locking methods of this instance + // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block + property Safe: PSynlocker read fSafe; + /// access to the internal TPendingTaskListItem.Timestamp stored value + // - corresponding to the current time + // - default implementation is to return GetTickCount64, with a 16 ms + // typical resolution under Windows + property Timestamp: Int64 read GetTimestamp; + /// how many pending tasks are currently defined + property Count: integer read GetCount; + /// direct low-level access to the internal task list + // - warning: this dynamic array length is the list capacity: use Count + // property to retrieve the exact number of stored items + // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block for + // thread-safe access to this array + // - items are stored in increasing Timestamp, i.e. the first item is + // the next one which would be returned by the NextPendingTask method + property Task: TPendingTaskListItemDynArray read fTask; + end; + +{$ifndef LVCL} // LVCL does not implement TEvent + +type + {$M+} + TSynBackgroundThreadAbstract = class; + TSynBackgroundThreadEvent = class; + {$M-} + + /// idle method called by TSynBackgroundThreadAbstract in the caller thread + // during remote blocking process in a background thread + // - typical use is to run Application.ProcessMessages, e.g. for + // TSQLRestClientURI.URI() to provide a responsive UI even in case of slow + // blocking remote access + // - provide the time elapsed (in milliseconds) from the request start (can be + // used e.g. to popup a temporary message to wait) + // - is call once with ElapsedMS=0 at request start + // - is call once with ElapsedMS=-1 at request ending + // - see TLoginForm.OnIdleProcess and OnIdleProcessForm in mORMotUILogin.pas + TOnIdleSynBackgroundThread = procedure(Sender: TSynBackgroundThreadAbstract; + ElapsedMS: Integer) of object; + + /// event prototype used e.g. by TSynBackgroundThreadAbstract callbacks + // - a similar signature is defined in SynCrtSock and LVCL.Classes + TNotifyThreadEvent = procedure(Sender: TThread) of object; + + /// abstract TThread with its own execution content + // - you should not use this class directly, but use either + // TSynBackgroundThreadMethodAbstract / TSynBackgroundThreadEvent / + // TSynBackgroundThreadMethod and provide a much more convenient callback + TSynBackgroundThreadAbstract = class(TThread) + protected + fProcessEvent: TEvent; + fOnBeforeExecute: TNotifyThreadEvent; + fOnAfterExecute: TNotifyThreadEvent; + fThreadName: RawUTF8; + fExecute: (exCreated,exRun,exFinished); + fExecuteLoopPause: boolean; + procedure SetExecuteLoopPause(dopause: boolean); + /// where the main process takes place + procedure Execute; override; + procedure ExecuteLoop; virtual; abstract; + public + /// initialize the thread + // - you could define some callbacks to nest the thread execution, e.g. + // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread, or + // at least set OnAfterExecute to TSynLogFamily.OnThreadEnded + constructor Create(const aThreadName: RawUTF8; OnBeforeExecute: TNotifyThreadEvent=nil; + OnAfterExecute: TNotifyThreadEvent=nil; CreateSuspended: boolean=false); reintroduce; + /// release used resources + destructor Destroy; override; + {$ifndef HASTTHREADSTART} + /// method to be called to start the thread + // - Resume is deprecated in the newest RTL, since some OS - e.g. Linux - + // do not implement this pause/resume feature; we define here this method + // for older versions of Delphi + procedure Start; + {$endif} + {$ifdef HASTTHREADTERMINATESET} + /// properly terminate the thread + // - called by TThread.Terminate + procedure TerminatedSet; override; + {$else} + /// properly terminate the thread + // - called by reintroduced Terminate + procedure TerminatedSet; virtual; + /// reintroduced to call TeminatedSet + procedure Terminate; reintroduce; + {$endif} + /// wait for Execute/ExecuteLoop to be ended (i.e. fExecute<>exRun) + procedure WaitForNotExecuting(maxMS: integer=500); + /// temporary stop the execution of ExecuteLoop, until set back to false + // - may be used e.g. by TSynBackgroundTimer to delay the process of + // background tasks + property Pause: boolean read fExecuteLoopPause write SetExecuteLoopPause; + /// access to the low-level associated event used to notify task execution + // to the background thread + // - you may call ProcessEvent.SetEvent to trigger the internal process loop + property ProcessEvent: TEvent read fProcessEvent; + /// defined as public since may be used to terminate the processing methods + property Terminated; + end; + + /// state machine status of the TSynBackgroundThreadAbstract process + TSynBackgroundThreadProcessStep = ( + flagIdle, flagStarted, flagFinished, flagDestroying); + + /// state machine statuses of the TSynBackgroundThreadAbstract process + TSynBackgroundThreadProcessSteps = set of TSynBackgroundThreadProcessStep; + + /// abstract TThread able to run a method in its own execution content + // - typical use is a background thread for processing data or remote access, + // while the UI will be still responsive by running OnIdle event in loop: see + // e.g. how TSQLRestClientURI.OnIdle handle this in mORMot.pas unit + // - you should not use this class directly, but inherit from it and override + // the Process method, or use either TSynBackgroundThreadEvent / + // TSynBackgroundThreadMethod and provide a much more convenient callback + TSynBackgroundThreadMethodAbstract = class(TSynBackgroundThreadAbstract) + protected + fCallerEvent: TEvent; + fParam: pointer; + fCallerThreadID: TThreadID; + fBackgroundException: Exception; + fOnIdle: TOnIdleSynBackgroundThread; + fOnBeforeProcess: TNotifyThreadEvent; + fOnAfterProcess: TNotifyThreadEvent; + fPendingProcessFlag: TSynBackgroundThreadProcessStep; + fPendingProcessLock: TSynLocker; + procedure ExecuteLoop; override; + function OnIdleProcessNotify(start: Int64): integer; + function GetOnIdleBackgroundThreadActive: boolean; + function GetPendingProcess: TSynBackgroundThreadProcessStep; + procedure SetPendingProcess(State: TSynBackgroundThreadProcessStep); + // returns flagIdle if acquired, flagDestroying if terminated + function AcquireThread: TSynBackgroundThreadProcessStep; + procedure WaitForFinished(start: Int64; const onmainthreadidle: TNotifyEvent); + /// called by Execute method when fProcessParams<>nil and fEvent is notified + procedure Process; virtual; abstract; + public + /// initialize the thread + // - if aOnIdle is not set (i.e. equals nil), it will simply wait for + // the background process to finish until RunAndWait() will return + // - you could define some callbacks to nest the thread execution, e.g. + // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread + constructor Create(aOnIdle: TOnIdleSynBackgroundThread; + const aThreadName: RawUTF8; OnBeforeExecute: TNotifyThreadEvent=nil; + OnAfterExecute: TNotifyThreadEvent=nil); reintroduce; + /// finalize the thread + destructor Destroy; override; + /// launch Process abstract method asynchronously in the background thread + // - wait until process is finished, calling OnIdle() callback in + // the meanwhile + // - any exception raised in background thread will be translated in the + // caller thread + // - returns false if self is not set, or if called from the same thread + // as it is currently processing (to avoid race condition from OnIdle() + // callback) + // - returns true when the background process is finished + // - OpaqueParam will be used to specify a thread-safe content for the + // background process + // - this method is thread-safe, that is it will wait for any started process + // already launch by another thread: you may call this method from any + // thread, even if its main purpose is to be called from the main UI thread + function RunAndWait(OpaqueParam: pointer): boolean; + /// set a callback event to be executed in loop during remote blocking + // process, e.g. to refresh the UI during a somewhat long request + // - you can assign a callback to this property, calling for instance + // Application.ProcessMessages, to execute the remote request in a + // background thread, but let the UI still be reactive: the + // TLoginForm.OnIdleProcess and OnIdleProcessForm methods of + // mORMotUILogin.pas will match this property expectations + // - if OnIdle is not set (i.e. equals nil), it will simply wait for + // the background process to finish until RunAndWait() will return + property OnIdle: TOnIdleSynBackgroundThread read fOnIdle write fOnIdle; + /// TRUE if the background thread is active, and OnIdle event is called + // during process + // - to be used e.g. to ensure no re-entrance from User Interface messages + property OnIdleBackgroundThreadActive: Boolean read GetOnIdleBackgroundThreadActive; + /// optional callback event triggered in Execute before each Process + property OnBeforeProcess: TNotifyThreadEvent read fOnBeforeProcess write fOnBeforeProcess; + /// optional callback event triggered in Execute after each Process + property OnAfterProcess: TNotifyThreadEvent read fOnAfterProcess write fOnAfterProcess; + end; + + /// background process method called by TSynBackgroundThreadEvent + // - will supply the OpaqueParam parameter as provided to RunAndWait() + // method when the Process virtual method will be executed + TOnProcessSynBackgroundThread = procedure(Sender: TSynBackgroundThreadEvent; + ProcessOpaqueParam: pointer) of object; + + /// allow background thread process of a method callback + TSynBackgroundThreadEvent = class(TSynBackgroundThreadMethodAbstract) + protected + fOnProcess: TOnProcessSynBackgroundThread; + /// just call the OnProcess handler + procedure Process; override; + public + /// initialize the thread + // - if aOnIdle is not set (i.e. equals nil), it will simply wait for + // the background process to finish until RunAndWait() will return + constructor Create(aOnProcess: TOnProcessSynBackgroundThread; + aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); reintroduce; + /// provide a method handler to be execute in the background thread + // - triggered by RunAndWait() method - which will wait until finished + // - the OpaqueParam as specified to RunAndWait() will be supplied here + property OnProcess: TOnProcessSynBackgroundThread read fOnProcess write fOnProcess; + end; + + /// allow background thread process of a variable TThreadMethod callback + TSynBackgroundThreadMethod = class(TSynBackgroundThreadMethodAbstract) + protected + /// just call the TThreadMethod, as supplied to RunAndWait() + procedure Process; override; + public + /// run once the supplied TThreadMethod callback + // - use this method, and not the inherited RunAndWait() + procedure RunAndWait(Method: TThreadMethod); reintroduce; + end; + + /// background process procedure called by TSynBackgroundThreadProcedure + // - will supply the OpaqueParam parameter as provided to RunAndWait() + // method when the Process virtual method will be executed + TOnProcessSynBackgroundThreadProc = procedure(ProcessOpaqueParam: pointer); + + /// allow background thread process of a procedure callback + TSynBackgroundThreadProcedure = class(TSynBackgroundThreadMethodAbstract) + protected + fOnProcess: TOnProcessSynBackgroundThreadProc; + /// just call the OnProcess handler + procedure Process; override; + public + /// initialize the thread + // - if aOnIdle is not set (i.e. equals nil), it will simply wait for + // the background process to finish until RunAndWait() will return + constructor Create(aOnProcess: TOnProcessSynBackgroundThreadProc; + aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); reintroduce; + /// provide a procedure handler to be execute in the background thread + // - triggered by RunAndWait() method - which will wait until finished + // - the OpaqueParam as specified to RunAndWait() will be supplied here + property OnProcess: TOnProcessSynBackgroundThreadProc read fOnProcess write fOnProcess; + end; + + /// an exception which would be raised by TSynParallelProcess + ESynParallelProcess = class(ESynException); + + /// callback implementing some parallelized process for TSynParallelProcess + // - if 0<=IndexStart<=IndexStop, it should execute some process + TSynParallelProcessMethod = procedure(IndexStart, IndexStop: integer) of object; + + /// thread executing process for TSynParallelProcess + TSynParallelProcessThread = class(TSynBackgroundThreadMethodAbstract) + protected + fMethod: TSynParallelProcessMethod; + fIndexStart, fIndexStop: integer; + procedure Start(Method: TSynParallelProcessMethod; IndexStart,IndexStop: integer); + /// executes fMethod(fIndexStart,fIndexStop) + procedure Process; override; + public + end; + + /// allow parallel execution of an index-based process in a thread pool + // - will create its own thread pool, then execute any method by spliting the + // work into each thread + TSynParallelProcess = class(TSynPersistentLock) + protected + fThreadName: RawUTF8; + fPool: array of TSynParallelProcessThread; + fThreadPoolCount: integer; + fParallelRunCount: integer; + public + /// initialize the thread pool + // - you could define some callbacks to nest the thread execution, e.g. + // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread + // - up to MaxThreadPoolCount=32 threads could be setup (you may allow a + // bigger value, but interrest of this thread pool is to have its process + // saturating each CPU core) + // - if ThreadPoolCount is 0, no thread would be created, and process + // would take place in the current thread + constructor Create(ThreadPoolCount: integer; const ThreadName: RawUTF8; + OnBeforeExecute: TNotifyThreadEvent=nil; OnAfterExecute: TNotifyThreadEvent=nil; + MaxThreadPoolCount: integer = 32); reintroduce; virtual; + /// finalize the thread pool + destructor Destroy; override; + /// run a method in parallel, and wait for the execution to finish + // - will split Method[0..MethodCount-1] execution over the threads + // - in case of any exception during process, an ESynParallelProcess + // exception would be raised by this method + // - if OnMainThreadIdle is set, the current thread (which is expected to be + // e.g. the main UI thread) won't process anything, but call this event + // during waiting for the background threads + procedure ParallelRunAndWait(const Method: TSynParallelProcessMethod; + MethodCount: integer; const OnMainThreadIdle: TNotifyEvent = nil); + published + /// how many threads have been activated + property ParallelRunCount: integer read fParallelRunCount; + /// how many threads are currently in this instance thread pool + property ThreadPoolCount: integer read fThreadPoolCount; + /// some text identifier, used to distinguish each owned thread + property ThreadName: RawUTF8 read fThreadName; + end; + + TSynBackgroundThreadProcess = class; + + /// event callback executed periodically by TSynBackgroundThreadProcess + // - Event is wrTimeout after the OnProcessMS waiting period + // - Event is wrSignaled if ProcessEvent.SetEvent has been called + TOnSynBackgroundThreadProcess = procedure(Sender: TSynBackgroundThreadProcess; + Event: TWaitResult) of object; + + /// TThread able to run a method at a given periodic pace + TSynBackgroundThreadProcess = class(TSynBackgroundThreadAbstract) + protected + fOnProcess: TOnSynBackgroundThreadProcess; + fOnException: TNotifyEvent; + fOnProcessMS: cardinal; + fStats: TSynMonitor; + procedure ExecuteLoop; override; + public + /// initialize the thread for a periodic task processing + // - aOnProcess would be called when ProcessEvent.SetEvent is called or + // aOnProcessMS milliseconds period was elapse since last process + // - if aOnProcessMS is 0, will wait until ProcessEvent.SetEvent is called + // - you could define some callbacks to nest the thread execution, e.g. + // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread + constructor Create(const aThreadName: RawUTF8; + aOnProcess: TOnSynBackgroundThreadProcess; aOnProcessMS: cardinal; + aOnBeforeExecute: TNotifyThreadEvent=nil; + aOnAfterExecute: TNotifyThreadEvent=nil; + aStats: TSynMonitorClass=nil; CreateSuspended: boolean=false); reintroduce; virtual; + /// finalize the thread + destructor Destroy; override; + /// access to the implementation event of the periodic task + property OnProcess: TOnSynBackgroundThreadProcess read fOnProcess; + /// event callback executed when OnProcess did raise an exception + // - supplied Sender parameter is the raised Exception instance + property OnException: TNotifyEvent read fOnException write fOnException; + published + /// access to the delay, in milliseconds, of the periodic task processing + property OnProcessMS: cardinal read fOnProcessMS write fOnProcessMS; + /// processing statistics + // - may be nil if aStats was nil in the class constructor + property Stats: TSynMonitor read fStats; + end; + + TSynBackgroundTimer = class; + + /// event callback executed periodically by TSynBackgroundThreadProcess + // - Event is wrTimeout after the OnProcessMS waiting period + // - Event is wrSignaled if ProcessEvent.SetEvent has been called + // - Msg is '' if there is no pending message in this task FIFO + // - Msg is set for each pending message in this task FIFO + TOnSynBackgroundTimerProcess = procedure(Sender: TSynBackgroundTimer; + Event: TWaitResult; const Msg: RawUTF8) of object; + + /// used by TSynBackgroundTimer internal registration list + TSynBackgroundTimerTask = record + OnProcess: TOnSynBackgroundTimerProcess; + Secs: cardinal; + NextTix: Int64; + FIFO: TRawUTF8DynArray; + end; + /// stores TSynBackgroundTimer internal registration list + TSynBackgroundTimerTaskDynArray = array of TSynBackgroundTimerTask; + + /// TThread able to run one or several tasks at a periodic pace in a + // background thread + // - as used e.g. by TSQLRest.TimerEnable/TimerDisable methods, via the + // inherited TSQLRestBackgroundTimer + // - each process can have its own FIFO of text messages + // - if you expect to update some GUI, you should rather use a TTimer + // component (with a period of e.g. 200ms), since TSynBackgroundTimer will + // use its own separated thread + TSynBackgroundTimer = class(TSynBackgroundThreadProcess) + protected + fTask: TSynBackgroundTimerTaskDynArray; + fTasks: TDynArray; + fTaskLock: TSynLocker; + procedure EverySecond(Sender: TSynBackgroundThreadProcess; Event: TWaitResult); + function Find(const aProcess: TMethod): integer; + function Add(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsg: RawUTF8; aExecuteNow: boolean): boolean; + public + /// initialize the thread for a periodic task processing + // - you could define some callbacks to nest the thread execution, e.g. + // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread, as + // made by TSQLRestBackgroundTimer.Create + constructor Create(const aThreadName: RawUTF8; + aOnBeforeExecute: TNotifyThreadEvent=nil; aOnAfterExecute: TNotifyThreadEvent=nil; + aStats: TSynMonitorClass=nil); reintroduce; virtual; + /// finalize the thread + destructor Destroy; override; + /// define a process method for a task running on a periodic number of seconds + // - for background process on a mORMot service, consider using TSQLRest + // TimerEnable/TimerDisable methods, and its associated BackgroundTimer thread + procedure Enable(aOnProcess: TOnSynBackgroundTimerProcess; aOnProcessSecs: cardinal); + /// undefine a task running on a periodic number of seconds + // - aOnProcess should have been registered by a previous call to Enable() method + // - returns true on success, false if the supplied task was not registered + // - for background process on a mORMot service, consider using TSQLRestServer + // TimerEnable/TimerDisable methods, and their TSynBackgroundTimer thread + function Disable(aOnProcess: TOnSynBackgroundTimerProcess): boolean; + /// add a message to be processed during the next execution of a task + // - supplied message will be added to the internal FIFO list associated + // with aOnProcess, then supplied to as aMsg parameter for each call + // - if aExecuteNow is true, won't wait for the next aOnProcessSecs occurence + // - aOnProcess should have been registered by a previous call to Enable() method + // - returns true on success, false if the supplied task was not registered + function EnQueue(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsg: RawUTF8; aExecuteNow: boolean=false): boolean; overload; + /// add a message to be processed during the next execution of a task + // - supplied message will be added to the internal FIFO list associated + // with aOnProcess, then supplied to as aMsg parameter for each call + // - if aExecuteNow is true, won't wait for the next aOnProcessSecs occurence + // - aOnProcess should have been registered by a previous call to Enable() method + // - returns true on success, false if the supplied task was not registered + function EnQueue(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsgFmt: RawUTF8; const Args: array of const; aExecuteNow: boolean=false): boolean; overload; + /// remove a message from the processing list + // - supplied message will be searched in the internal FIFO list associated + // with aOnProcess, then removed from the list if found + // - aOnProcess should have been registered by a previous call to Enable() method + // - returns true on success, false if the supplied message was not registered + function DeQueue(aOnProcess: TOnSynBackgroundTimerProcess; const aMsg: RawUTF8): boolean; + /// execute a task without waiting for the next aOnProcessSecs occurence + // - aOnProcess should have been registered by a previous call to Enable() method + // - returns true on success, false if the supplied task was not registered + function ExecuteNow(aOnProcess: TOnSynBackgroundTimerProcess): boolean; + /// returns true if there is currenly one task processed + function Processing: boolean; + /// wait until no background task is processed + procedure WaitUntilNotProcessing(timeoutsecs: integer=10); + /// low-level access to the internal task list + property Task: TSynBackgroundTimerTaskDynArray read fTask; + /// low-level access to the internal task mutex + property TaskLock: TSynLocker read fTaskLock; + end; + + /// the current state of a TBlockingProcess instance + TBlockingEvent = (evNone,evWaiting,evTimeOut,evRaised); + + {$M+} + /// a semaphore used to wait for some process to be finished + // - used e.g. by TBlockingCallback in mORMot.pas + // - once created, process would block via a WaitFor call, which would be + // released when NotifyFinished is called by the process background thread + TBlockingProcess = class(TEvent) + protected + fTimeOutMs: integer; + fEvent: TBlockingEvent; + fSafe: PSynLocker; + fOwnedSafe: boolean; + procedure ResetInternal; virtual; // override to reset associated params + public + /// initialize the semaphore instance + // - specify a time out millliseconds period after which blocking execution + // should be handled as failure (if 0 is set, default 3000 would be used) + // - an associated mutex shall be supplied + constructor Create(aTimeOutMs: integer; aSafe: PSynLocker); reintroduce; overload; virtual; + /// initialize the semaphore instance + // - specify a time out millliseconds period after which blocking execution + // should be handled as failure (if 0 is set, default 3000 would be used) + // - an associated mutex would be created and owned by this instance + constructor Create(aTimeOutMs: integer); reintroduce; overload; virtual; + /// finalize the instance + destructor Destroy; override; + /// called to wait for NotifyFinished() to be called, or trigger timeout + // - returns the final state of the process, i.e. evRaised or evTimeOut + function WaitFor: TBlockingEvent; reintroduce; overload; virtual; + /// called to wait for NotifyFinished() to be called, or trigger timeout + // - returns the final state of the process, i.e. evRaised or evTimeOut + function WaitFor(TimeOutMS: integer): TBlockingEvent; reintroduce; overload; + /// should be called by the background process when it is finished + // - the caller would then let its WaitFor method return + // - returns TRUE on success (i.e. status was not evRaised or evTimeout) + // - if the instance is already locked (e.g. when retrieved from + // TBlockingProcessPool.FromCallLocked), you may set alreadyLocked=TRUE + function NotifyFinished(alreadyLocked: boolean=false): boolean; virtual; + /// just a wrapper to reset the internal Event state to evNone + // - may be used to re-use the same TBlockingProcess instance, after + // a successfull WaitFor/NotifyFinished process + // - returns TRUE on success (i.e. status was not evWaiting), setting + // the current state to evNone, and the Call property to 0 + // - if there is a WaitFor currently in progress, returns FALSE + function Reset: boolean; virtual; + /// just a wrapper around fSafe^.Lock + procedure Lock; + /// just a wrapper around fSafe^.Unlock + procedure Unlock; + published + /// the current state of process + // - use Reset method to re-use this instance after a WaitFor process + property Event: TBlockingEvent read fEvent; + /// the time out period, in ms, as defined at constructor level + property TimeOutMs: integer read fTimeOutMS; + end; + {$M-} + + /// used to identify each TBlockingProcessPool call + // - allow to match a given TBlockingProcessPoolItem semaphore + TBlockingProcessPoolCall = type integer; + + /// a semaphore used in the TBlockingProcessPool + // - such semaphore have a Call field to identify each execution + TBlockingProcessPoolItem = class(TBlockingProcess) + protected + fCall: TBlockingProcessPoolCall; + procedure ResetInternal; override; + published + /// an unique identifier, when owned by a TBlockingProcessPool + // - Reset would restore this field to its 0 default value + property Call: TBlockingProcessPoolCall read fCall; + end; + + /// class-reference type (metaclass) of a TBlockingProcess + TBlockingProcessPoolItemClass = class of TBlockingProcessPoolItem; + + /// manage a pool of TBlockingProcessPoolItem instances + // - each call will be identified via a TBlockingProcessPoolCall unique value + // - to be used to emulate e.g. blocking execution from an asynchronous + // event-driven DDD process + // - it would also allow to re-use TEvent system resources + TBlockingProcessPool = class(TSynPersistent) + protected + fClass: TBlockingProcessPoolItemClass; + fPool: TObjectListLocked; + fCallCounter: TBlockingProcessPoolCall; // set TBlockingProcessPoolItem.Call + public + /// initialize the pool, for a given implementation class + constructor Create(aClass: TBlockingProcessPoolItemClass=nil); reintroduce; + /// finalize the pool + // - would also force all pending WaitFor to trigger a evTimeOut + destructor Destroy; override; + /// book a TBlockingProcess from the internal pool + // - returns nil on error (e.g. the instance is destroying) + // - or returns the blocking process instance corresponding to this call; + // its Call property would identify the call for the asynchronous callback, + // then after WaitFor, the Reset method should be run to release the mutex + // for the pool + function NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem; virtual; + /// retrieve a TBlockingProcess from its call identifier + // - may be used e.g. from the callback of the asynchronous process + // to set some additional parameters to the inherited TBlockingProcess, + // then call NotifyFinished to release the caller WaitFor + // - if leavelocked is TRUE, the returned instance would be locked: caller + // should execute result.Unlock or NotifyFinished(true) after use + function FromCall(call: TBlockingProcessPoolCall; + locked: boolean=false): TBlockingProcessPoolItem; virtual; + end; + +/// allow to fix TEvent.WaitFor() method for Kylix +// - under Windows or with FPC, will call original TEvent.WaitFor() method +function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult; + +/// allow to fix TEvent.WaitFor(Event,INFINITE) method for Kylix +// - under Windows or with FPC, will call original TEvent.WaitFor() method +procedure FixedWaitForever(Event: TEvent); + +{$endif LVCL} // LVCL does not implement TEvent + + +{ ************ System Analysis types and classes ************************** } + +type + /// store CPU and RAM usage for a given process + // - as used by TSystemUse class + TSystemUseData = packed record + /// when the data has been sampled + Timestamp: TDateTime; + /// percent of current Kernel-space CPU usage for this process + Kernel: single; + /// percent of current User-space CPU usage for this process + User: single; + /// how many KB of working memory are used by this process + WorkKB: cardinal; + /// how many KB of virtual memory are used by this process + VirtualKB: cardinal; + end; + /// store CPU and RAM usage history for a given process + // - as returned by TSystemUse.History + TSystemUseDataDynArray = array of TSystemUseData; + + /// low-level structure used to compute process memory and CPU usage + {$ifdef FPC_OR_UNICODE}TProcessInfo = record private + {$else}TProcessInfo = object protected{$endif} + {$ifdef MSWINDOWS} + fSysPrevIdle, fSysPrevKernel, fSysPrevUser, + fDiffIdle, fDiffKernel, fDiffUser, fDiffTotal: Int64; + {$endif} + public + /// initialize the system/process resource tracking + function Init: boolean; + /// to be called before PerSystem() or PerProcess() iteration + function Start: boolean; + /// percent of current Idle/Kernel/User CPU usage for all processes + function PerSystem(out Idle,Kernel,User: currency): boolean; + /// retrieve CPU and RAM usage for a given process + function PerProcess(PID: cardinal; Now: PDateTime; out Data: TSystemUseData; + var PrevKernel, PrevUser: Int64): boolean; + end; + + /// event handler which may be executed by TSystemUse.BackgroundExecute + // - called just after the measurement of each process CPU and RAM consumption + // - run from the background thread, so should not directly make VCL calls, + // unless BackgroundExecute is run from a VCL timer + TOnSystemUseMeasured = procedure(ProcessID: integer; const Data: TSystemUseData) of object; + + /// internal storage of CPU and RAM usage for one process + TSystemUseProcess = record + ID: integer; + Data: TSystemUseDataDynArray; + PrevKernel: Int64; + PrevUser: Int64; + end; + /// internal storage of CPU and RAM usage for a set of processes + TSystemUseProcessDynArray = array of TSystemUseProcess; + + /// monitor CPU and RAM usage of one or several processes + // - you should execute BackgroundExecute on a regular pace (e.g. every second) + // to gather low-level CPU and RAM information for the given set of processes + // - is able to keep an history of latest sample values + // - use Current class function to access a process-wide instance + TSystemUse = class(TSynPersistentLock) + protected + fProcess: TSystemUseProcessDynArray; + fProcesses: TDynArray; + fDataIndex: integer; + fProcessInfo: TProcessInfo; + fHistoryDepth: integer; + fOnMeasured: TOnSystemUseMeasured; + fTimer: TSynBackgroundTimer; + fUnsubscribeProcessOnAccessError: boolean; + function ProcessIndex(aProcessID: integer): integer; + public + /// a TSynBackgroundThreadProcess compatible event + // - matches TOnSynBackgroundTimerProcess callback signature + // - to be supplied e.g. to a TSynBackgroundTimer.Enable method so that it + // will run every few seconds and retrieve the CPU and RAM use + procedure BackgroundExecute(Sender: TSynBackgroundTimer; + Event: TWaitResult; const Msg: RawUTF8); + /// a VCL's TTimer.OnTimer compatible event + // - to be run every few seconds and retrieve the CPU and RAM use: + // ! tmrSystemUse.Interval := 10000; // every 10 seconds + // ! tmrSystemUse.OnTimer := TSystemUse.Current.OnTimerExecute; + procedure OnTimerExecute(Sender: TObject); + /// track the CPU and RAM usage of the supplied set of Process ID + // - any aProcessID[]=0 will be replaced by the current process ID + // - you can specify the number of sample values for the History() method + // - you should then execute the BackgroundExecute method of this instance + // in a VCL timer or from a TSynBackgroundTimer.Enable() registration + constructor Create(const aProcessID: array of integer; + aHistoryDepth: integer=60); reintroduce; overload; virtual; + /// track the CPU and RAM usage of the current process + // - you can specify the number of sample values for the History() method + // - you should then execute the BackgroundExecute method of this instance + // in a VCL timer or from a TSynBackgroundTimer.Enable() registration + constructor Create(aHistoryDepth: integer=60); reintroduce; overload; virtual; + /// add a Process ID to the internal tracking list + procedure Subscribe(aProcessID: integer); + /// remove a Process ID from the internal tracking list + function Unsubscribe(aProcessID: integer): boolean; + /// returns the total (Kernel+User) CPU usage percent of the supplied process + // - aProcessID=0 will return information from the current process + // - returns -1 if the Process ID was not registered via Create/Subscribe + function Percent(aProcessID: integer=0): single; overload; + /// returns the Kernel-space CPU usage percent of the supplied process + // - aProcessID=0 will return information from the current process + // - returns -1 if the Process ID was not registered via Create/Subscribe + function PercentKernel(aProcessID: integer=0): single; overload; + /// returns the User-space CPU usage percent of the supplied process + // - aProcessID=0 will return information from the current process + // - returns -1 if the Process ID was not registered via Create/Subscribe + function PercentUser(aProcessID: integer=0): single; overload; + /// returns the total (Work+Paged) RAM use of the supplied process, in KB + // - aProcessID=0 will return information from the current process + // - returns 0 if the Process ID was not registered via Create/Subscribe + function KB(aProcessID: integer=0): cardinal; overload; + /// percent of current Idle/Kernel/User CPU usage for all processes + function PercentSystem(out Idle,Kernel,User: currency): boolean; + /// returns the detailed CPU and RAM usage percent of the supplied process + // - aProcessID=0 will return information from the current process + // - returns -1 if the Process ID was not registered via Create/Subscribe + function Data(out aData: TSystemUseData; aProcessID: integer=0): boolean; overload; + /// returns the detailed CPU and RAM usage percent of the supplied process + // - aProcessID=0 will return information from the current process + // - returns Timestamp=0 if the Process ID was not registered via Create/Subscribe + function Data(aProcessID: integer=0): TSystemUseData; overload; + /// returns total (Kernel+User) CPU usage percent history of the supplied process + // - aProcessID=0 will return information from the current process + // - returns nil if the Process ID was not registered via Create/Subscribe + // - returns the sample values as an array, starting from the last to the oldest + // - you can customize the maximum depth, with aDepth < HistoryDepth + function History(aProcessID: integer=0; aDepth: integer=0): TSingleDynArray; overload; + /// returns total (Kernel+User) CPU usage percent history of the supplied + // process, as a string of two digits values + // - aProcessID=0 will return information from the current process + // - returns '' if the Process ID was not registered via Create/Subscribe + // - you can customize the maximum depth, with aDepth < HistoryDepth + // - the memory history (in MB) can be optionally returned in aDestMemoryMB + function HistoryText(aProcessID: integer=0; aDepth: integer=0; + aDestMemoryMB: PRawUTF8=nil): RawUTF8; + {$ifndef NOVARIANTS} + /// returns total (Kernel+User) CPU usage percent history of the supplied process + // - aProcessID=0 will return information from the current process + // - returns null if the Process ID was not registered via Create/Subscribe + // - returns the sample values as a TDocVariant array, starting from the + // last to the oldest, with two digits precision (as currency values) + // - you can customize the maximum depth, with aDepth < HistoryDepth + function HistoryVariant(aProcessID: integer=0; aDepth: integer=0): variant; + {$endif} + /// access to a global instance, corresponding to the current process + // - its HistoryDepth will be of 60 items + class function Current(aCreateIfNone: boolean=true): TSystemUse; + /// returns detailed CPU and RAM usage history of the supplied process + // - aProcessID=0 will return information from the current process + // - returns nil if the Process ID was not registered via Create/Subscribe + // - returns the sample values as an array, starting from the last to the oldest + // - you can customize the maximum depth, with aDepth < HistoryDepth + function HistoryData(aProcessID: integer=0; aDepth: integer=0): TSystemUseDataDynArray; overload; + /// if any unexisting (e.g. closed/killed) process should be unregistered + // - e.g. if OpenProcess() API call fails + property UnsubscribeProcessOnAccessError: boolean + read fUnsubscribeProcessOnAccessError write fUnsubscribeProcessOnAccessError; + /// how many items are stored internally, and returned by the History() method + property HistoryDepth: integer read fHistoryDepth; + /// executed when TSystemUse.BackgroundExecute finished its measurement + property OnMeasured: TOnSystemUseMeasured read fOnMeasured write fOnMeasured; + /// low-level access to the associated timer running BackgroundExecute + // - equals nil if has been associated to no timer + property Timer: TSynBackgroundTimer read fTimer write fTimer; + end; + + /// stores information about a disk partition + TDiskPartition = packed record + /// the name of this partition + // - is the Volume name under Windows, or the Device name under POSIX + name: RawUTF8; + /// where this partition has been mounted + // - e.g. 'C:' or '/home' + // - you can use GetDiskInfo(mounted) to retrieve current space information + mounted: TFileName; + /// total size (in bytes) of this partition + size: QWord; + end; + /// stores information about several disk partitions + TDiskPartitions = array of TDiskPartition; + + /// value object able to gather information about the current system memory + TSynMonitorMemory = class(TSynPersistent) + protected + FAllocatedUsed: TSynMonitorOneSize; + FAllocatedReserved: TSynMonitorOneSize; + FMemoryLoadPercent: integer; + FPhysicalMemoryFree: TSynMonitorOneSize; + FVirtualMemoryFree: TSynMonitorOneSize; + FPagingFileTotal: TSynMonitorOneSize; + FPhysicalMemoryTotal: TSynMonitorOneSize; + FVirtualMemoryTotal: TSynMonitorOneSize; + FPagingFileFree: TSynMonitorOneSize; + fLastMemoryInfoRetrievedTix: cardinal; + procedure RetrieveMemoryInfo; virtual; + function GetAllocatedUsed: TSynMonitorOneSize; + function GetAllocatedReserved: TSynMonitorOneSize; + function GetMemoryLoadPercent: integer; + function GetPagingFileFree: TSynMonitorOneSize; + function GetPagingFileTotal: TSynMonitorOneSize; + function GetPhysicalMemoryFree: TSynMonitorOneSize; + function GetPhysicalMemoryTotal: TSynMonitorOneSize; + function GetVirtualMemoryFree: TSynMonitorOneSize; + function GetVirtualMemoryTotal: TSynMonitorOneSize; + public + /// initialize the class, and its nested TSynMonitorOneSize instances + constructor Create(aTextNoSpace: boolean); reintroduce; + /// finalize the class, and its nested TSynMonitorOneSize instances + destructor Destroy; override; + /// some text corresponding to current 'free/total' memory information + // - returns e.g. '10.3 GB / 15.6 GB' + class function FreeAsText(nospace: boolean=false): ShortString; + /// how many physical memory is currently installed, as text (e.g. '32 GB'); + class function PhysicalAsText(nospace: boolean=false): TShort16; + /// returns a JSON object with the current system memory information + // - numbers would be given in KB (Bytes shl 10) + class function ToJSON: RawUTF8; + {$ifndef NOVARIANTS} + /// fill a TDocVariant with the current system memory information + // - numbers would be given in KB (Bytes shl 10) + class function ToVariant: variant; + {$endif} + published + /// Total of allocated memory used by the program + property AllocatedUsed: TSynMonitorOneSize read GetAllocatedUsed; + /// Total of allocated memory reserved by the program + property AllocatedReserved: TSynMonitorOneSize read GetAllocatedReserved; + /// Percent of memory in use for the system + property MemoryLoadPercent: integer read GetMemoryLoadPercent; + /// Total of physical memory for the system + property PhysicalMemoryTotal: TSynMonitorOneSize read GetPhysicalMemoryTotal; + /// Free of physical memory for the system + property PhysicalMemoryFree: TSynMonitorOneSize read GetPhysicalMemoryFree; + /// Total of paging file for the system + property PagingFileTotal: TSynMonitorOneSize read GetPagingFileTotal; + /// Free of paging file for the system + property PagingFileFree: TSynMonitorOneSize read GetPagingFileFree; + {$ifdef MSWINDOWS} + /// Total of virtual memory for the system + // - property not defined under Linux, since not applying to this OS + property VirtualMemoryTotal: TSynMonitorOneSize read GetVirtualMemoryTotal; + /// Free of virtual memory for the system + // - property not defined under Linux, since not applying to this OS + property VirtualMemoryFree: TSynMonitorOneSize read GetVirtualMemoryFree; + {$endif} + end; + + /// value object able to gather information about a system drive + TSynMonitorDisk = class(TSynPersistent) + protected + fName: TFileName; + {$ifdef MSWINDOWS} + fVolumeName: TFileName; + {$endif} + fAvailableSize: TSynMonitorOneSize; + fFreeSize: TSynMonitorOneSize; + fTotalSize: TSynMonitorOneSize; + fLastDiskInfoRetrievedTix: cardinal; + procedure RetrieveDiskInfo; virtual; + function GetName: TFileName; + function GetAvailable: TSynMonitorOneSize; + function GetFree: TSynMonitorOneSize; + function GetTotal: TSynMonitorOneSize; + public + /// initialize the class, and its nested TSynMonitorOneSize instances + constructor Create; override; + /// finalize the class, and its nested TSynMonitorOneSize instances + destructor Destroy; override; + /// some text corresponding to current 'free/total' disk information + // - could return e.g. 'D: 64.4 GB / 213.4 GB' + class function FreeAsText: RawUTF8; + published + /// the disk name + property Name: TFileName read GetName; + {$ifdef MSWINDOWS} + /// the volume name (only available on Windows) + property VolumeName: TFileName read fVolumeName write fVolumeName; + /// space currently available on this disk for the current user + // - may be less then FreeSize, if user quotas are specified (only taken + // into account under Windows) + property AvailableSize: TSynMonitorOneSize read GetAvailable; + {$endif MSWINDOWS} + /// free space currently available on this disk + property FreeSize: TSynMonitorOneSize read GetFree; + /// total space + property TotalSize: TSynMonitorOneSize read GetTotal; + end; + + /// hold low-level information about current memory usage + // - as filled by GetMemoryInfo() + TMemoryInfo = record + memtotal, memfree, filetotal, filefree, vmtotal, vmfree, + allocreserved, allocused: QWord; + percent: integer; + end; + + +/// retrieve low-level information about all mounted disk partitions of the system +// - returned partitions array is sorted by "mounted" ascending order +function GetDiskPartitions: TDiskPartitions; + +/// retrieve low-level information about all mounted disk partitions as text +// - returns e.g. under Linux +// '/ /dev/sda3 (19 GB), /boot /dev/sda2 (486.8 MB), /home /dev/sda4 (0.9 TB)' +// or under Windows 'C:\ System (115 GB), D:\ Data (99.3 GB)' +// - uses internally a cache unless nocache is true +// - includes the free space if withfreespace is true - e.g. '(80 GB / 115 GB)' +function GetDiskPartitionsText(nocache: boolean=false; + withfreespace: boolean=false; nospace: boolean=false): RawUTF8; + +/// returns a JSON object containing basic information about the computer +// - including Host, User, CPU, OS, freemem, freedisk... +function SystemInfoJson: RawUTF8; + +{$ifdef MSWINDOWS} + +/// a wrapper around EnumProcesses() PsAPI call +function EnumAllProcesses(out Count: Cardinal): TCardinalDynArray; + +/// a wrapper around QueryFullProcessImageNameW/GetModuleFileNameEx PsAPI call +function EnumProcessName(PID: Cardinal): RawUTF8; + +{$endif MSWINDOWS} + + +/// retrieve low-level information about current memory usage +// - as used by TSynMonitorMemory +// - under BSD, only memtotal/memfree/percent are properly returned +// - allocreserved and allocused are set only if withalloc is TRUE +function GetMemoryInfo(out info: TMemoryInfo; withalloc: boolean): boolean; + +/// retrieve low-level information about a given disk partition +// - as used by TSynMonitorDisk and GetDiskPartitionsText() +// - only under Windows the Quotas are applied separately to aAvailableBytes +// in respect to global aFreeBytes +function GetDiskInfo(var aDriveFolderOrFile: TFileName; + out aAvailableBytes, aFreeBytes, aTotalBytes: QWord + {$ifdef MSWINDOWS}; aVolumeName: PFileName = nil{$endif}): boolean; + + +implementation + +{$ifdef FPCLINUX} +uses + {$ifdef BSD} + ctypes, + sysctl, + {$else} + Linux, + {$endif BSD} + SynFPCLinux; +{$endif FPCLINUX} + + +{ ************ TSynTable generic types and classes ************************** } + +{$ifndef NOVARIANTS} + +{ TSynTableVariantType } + +var + SynTableVariantType: TCustomVariantType = nil; + +procedure TSynTableVariantType.Clear(var V: TVarData); +begin + //Assert(V.VType=SynTableVariantType.VarType); + TSynTableData(V).VValue := ''; // clean memory release + PPtrUInt(@V)^ := 0; // will set V.VType := varEmpty +end; + +procedure TSynTableVariantType.Copy(var Dest: TVarData; + const Source: TVarData; const Indirect: Boolean); +begin + //Assert(Source.VType=SynTableVariantType.VarType); + inherited Copy(Dest,Source,Indirect); // copy VType+VID+VTable + if not Indirect then + with TSynTableData(Dest) do begin + PtrInt(VValue) := 0; // avoid GPF + VValue := TSynTableData(Source).VValue; // copy by reference + end; +end; + +procedure TSynTableVariantType.IntGet(var Dest: TVarData; + const V: TVarData; Name: PAnsiChar); +begin + TSynTableData(V).GetFieldVariant(RawByteString(Name),variant(Dest)); +end; + +procedure TSynTableVariantType.IntSet(const V, Value: TVarData; + Name: PAnsiChar); +begin + TSynTableData(V).SetFieldValue(RawByteString(Name),Variant(Value)); +end; + +class function TSynTableVariantType.ToID(const V: Variant): integer; +var Data: TSynTableData absolute V; +begin + if Data.VType<>SynTableVariantType.VarType then + result := 0 else + result := Data.VID; +end; + +class function TSynTableVariantType.ToSBF(const V: Variant): TSBFString; +var Data: TSynTableData absolute V; +begin + if Data.VType<>SynTableVariantType.VarType then + result := '' else + result := Data.VValue; +end; + +class function TSynTableVariantType.ToTable(const V: Variant): TSynTable; +var Data: TSynTableData absolute V; +begin + if Data.VType<>SynTableVariantType.VarType then + result := nil else + result := Data.VTable; +end; + +{$endif NOVARIANTS} + + +{ TSynTable } + +{$ifdef CPUX86} +function SortQWord(const A,B: QWord): integer; +asm // Delphi x86 compiler is not efficient, and oldest even incorrect + mov ecx, [eax] + mov eax, [eax + 4] + cmp eax, [edx + 4] + jnz @nz + cmp ecx, [edx] + jz @0 +@nz: jnb @p + or eax, -1 + ret +@0: xor eax, eax + ret +@p: mov eax, 1 +end; + +function SortInt64(const A,B: Int64): integer; +asm // Delphi x86 compiler is not efficient at compiling below code + mov ecx, [eax] + mov eax, [eax + 4] + cmp eax, [edx + 4] + jnz @nz + cmp ecx, [edx] + jz @0 + jnb @p +@n: or eax, -1 + ret +@0: xor eax, eax + ret +@nz: jl @n +@p: mov eax, 1 +end; +{$endif} + +{$ifndef SORTCOMPAREMETHOD} + +function SortU8(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + result := PByte(P1)^-PByte(P2)^; + exit; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortU16(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + result := PWord(P1)^-PWord(P2)^; + exit; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortI32(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + result := PInteger(P1)^-PInteger(P2)^; + exit; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortDouble(P1,P2: PUTF8Char): PtrInt; +var V: Double; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + V := PDouble(P1)^-PDouble(P2)^; + if V<0 then + result := -1 else + if V=0 then + result := 0 else + result := 1; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortU24(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + result := PtrInt(PWord(P1)^)+PtrInt(P1[2])shl 16 + -PtrInt(PWord(P2)^)-PtrInt(P2[2]) shl 16; + exit; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarUInt32(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + result := FromVarUInt32(PByte(P1))-FromVarUInt32(PByte(P2)); + exit; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarInt32(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + result := FromVarInt32(PByte(P1))-FromVarInt32(PByte(P2)); + exit; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +{$ifdef CPU64} // PtrInt = Int64 -> so direct substraction works + +function SortI64(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + result := PInt64(P1)^-PInt64(P2)^ else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarUInt64(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + result := FromVarUInt64(PByte(P1))-FromVarUInt64(PByte(P2)) else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarInt64(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + result := FromVarInt64(PByte(P1))-FromVarInt64(PByte(P2)) else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +{$else} + +{$ifdef CPUX86} // circumvent comparison slowness (and QWord bug) + +function SortI64(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + result := SortInt64(PInt64(P1)^,PInt64(P2)^) else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarUInt64(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + result := SortQWord(FromVarUInt64(PByte(P1)),FromVarUInt64(PByte(P2))) else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarInt64(P1,P2: PUTF8Char): PtrInt; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + result := SortInt64(FromVarInt64(PByte(P1)),FromVarInt64(PByte(P2))) else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +{$else} + +function SortI64(P1,P2: PUTF8Char): PtrInt; +var V: Int64; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + V := PInt64(P1)^-PInt64(P2)^; + if V<0 then + result := -1 else + if V>0 then + result := 1 else + result := 0; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarUInt64(P1,P2: PUTF8Char): PtrInt; +var V1,V2: QWord; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + V1 := FromVarUInt64(PByte(P1)); + V2 := FromVarUInt64(PByte(P2)); + if V1>V2 then + result := 1 else + if V1=V2 then + result := 0 else + result := -1; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortVarInt64(P1,P2: PUTF8Char): PtrInt; +var V1,V2: Int64; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + V1 := FromVarInt64(PByte(P1)); + V2 := FromVarInt64(PByte(P2)); + if V1>V2 then + result := 1 else + if V1=V2 then + result := 0 else + result := -1; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +{$endif CPUX86} + +{$endif CPU64} + +function SortStr(P1,P2: PUTF8Char): PtrInt; +var L1, L2, L, i: PtrInt; + PB1, PB2: PByte; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + if PtrInt(P1^)<=$7F then begin + L1 := PtrInt(P1^); + inc(P1); + end else begin + PB1 := pointer(P1); + L1 := FromVarUInt32High(PB1); + P1 := pointer(PB1); + end; + if PtrInt(P2^)<=$7F then begin + L2 := PtrInt(P2^); + inc(P2); + end else begin + PB2 := pointer(P2); + L2 := FromVarUInt32High(PB2); + P2 := pointer(PB2); + end; + L := L1; + if L2>L then + L := L2; + for i := 0 to L-1 do begin + result := PtrInt(P1[i])-PtrInt(P2[i]); + if Result<>0 then + exit; + end; + result := L1-L2; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +function SortIStr(P1,P2: PUTF8Char): PtrInt; +var L1, L2, L, i: PtrInt; + PB1, PB2: PByte; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then begin + if PtrInt(P1^)<=$7F then begin + L1 := PtrInt(P1^); + inc(P1); + end else begin + PB1 := pointer(P1); + L1 := FromVarUInt32High(PB1); + P1 := pointer(PB1); + end; + if PtrInt(P2^)<=$7F then begin + L2 := PtrInt(P2^); + inc(P2); + end else begin + PB2 := pointer(P2); + L2 := FromVarUInt32High(PB2); + P2 := pointer(PB2); + end; + if L2>L1 then + L := L2 else + L := L1; + for i := 0 to L-1 do // NormToUpperAnsi7 works for both WinAnsi & UTF-8 + if NormToUpperAnsi7[P1[i]]<>NormToUpperAnsi7[P2[i]] then begin + result := PtrInt(P1[i])-PtrInt(P2[i]); + exit; + end; + result := L1-L2; + end else + result := 1 else // P2=nil + result := -1 else // P1=nil + result := 0; // P1=P2 +end; + +const + FIELD_SORT: array[TSynTableFieldType] of TUTF8Compare = ( + nil, // tftUnknown, + SortU8, SortU8, SortU16, SortU24, SortI32, SortI64, + // tftBoolean,tftUInt8,tftUInt16,tftUInt24,tftInt32,tftInt64, + SortI64, SortDouble, SortVarUInt32,SortVarInt32,SortVarUInt64, + // tftCurrency,tftDouble, tftVarUInt32, tftVarInt32,tftVarUInt64, + SortStr, SortStr, SortStr, nil, SortVarInt64); + // tftWinAnsi,tftUTF8, tftBlobInternal,tftBlobExternal,tftVarInt64); + +{$endif SORTCOMPAREMETHOD} + +const + FIELD_FIXEDSIZE: array[TSynTableFieldType] of Integer = ( + 0, // tftUnknown, + 1, 1, 2, 3, 4, 8, 8, 8, + // tftBoolean, tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64, tftCurrency, tftDouble + -1, -1, -1, // tftVarUInt32, tftVarInt32, tftVarUInt64 have -1 as size + -2, -2, -2, // tftWinAnsi, tftUTF8, tftBlobInternal have -2 as size + -3, // tftBlobExternal has -3 as size + -1); //tftVarInt64 + + // note: boolean is not in this set, because it can be 'true' or 'false' + FIELD_INTEGER: TSynTableFieldTypes = [ + tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64, + tftVarUInt32, tftVarInt32, tftVarUInt64, tftVarInt64]; + +function TSynTable.AddField(const aName: RawUTF8; + aType: TSynTableFieldType; aOptions: TSynTableFieldOptions): TSynTableFieldProperties; +var aSize: Integer; +begin + result := nil; + aSize := FIELD_FIXEDSIZE[aType]; + if (self=nil) or (aSize=0) or IsRowID(pointer(aName)) or + not PropNameValid(pointer(aName)) or (GetFieldFromName(aName)<>nil) then + exit; + result := TSynTableFieldProperties.Create; + if fAddedField=nil then + fAddedField := TList.Create; + fAddedField.Add(result); + result.Name := aName; + result.FieldType := aType; + if tfoUnique in aOptions then + Include(aOptions,tfoIndex); // create an index for faster Unique field + if aSize=-3 then // external field has no index available + aOptions := aOptions-[tfoIndex,tfoUnique]; + result.Options := aOptions; + if aSize>0 then begin + // fixed-size field should be inserted left-side of the stream + if (tfoIndex in aOptions) or (aSize and 3=0) then begin + // indexed field or size is alignment friendly: put left side + if not ((tfoIndex in aOptions) and (aSize and 3=0)) then + // indexed+aligned field -> set first, otherwise at variable or not indexed + while result.FieldNumber=0 then + result.FieldNumber := fFieldVariableIndex else + result.FieldNumber := fField.Count; + fField.Insert(result.FieldNumber,result); + end else begin + if (tfoIndex in aOptions) and (fFieldVariableIndex>=0) then begin + // indexed field should be added left side (faster access for sort) + result.FieldNumber := fFieldVariableIndex; + while result.FieldNumbernil) and ((RecordBuffer=nil) or (RecordBufferLen=0)) then begin + // no data yet -> use default + RecordBuffer := pointer(fDefaultRecordData); + RecordBufferLen := fDefaultRecordLength; + end; + if RecordBuffer=pointer(result) then + // update content code below will fail -> please correct calling code + raise ETableDataException.CreateUTF8('In-place call of %.UpdateFieldData',[self]); + if (self=nil) or (cardinal(FieldIndex)>=cardinal(fField.Count)) then begin + SetString(result,PAnsiChar(RecordBuffer),RecordBufferLen); + exit; + end; + F := TSynTableFieldProperties(fField.List[FieldIndex]); + NewSize := length(NewFieldData); + if NewSize=0 then begin + // no NewFieldData specified -> use default field data to be inserted + NewData := pointer(F.fDefaultFieldData); + NewSize := F.fDefaultFieldLength; + end else + NewData := pointer(NewFieldData); + Dest := GetData(RecordBuffer,F); + DestOffset := Dest-RecordBuffer; + // update content + OldSize := F.GetLength(Dest); + dec(RecordBufferLen,OldSize); + SetLength(Result,RecordBufferLen+NewSize); + MoveFast(RecordBuffer^,PByteArray(result)[0],DestOffset); + MoveFast(NewData^,PByteArray(result)[DestOffset],NewSize); + MoveFast(Dest[OldSize],PByteArray(result)[DestOffset+NewSize],RecordBufferLen-DestOffset); +end; + +constructor TSynTable.Create(const aTableName: RawUTF8); +begin + if not PropNameValid(pointer(aTableName)) then + raise ETableDataException.CreateUTF8('Invalid %.Create(%)',[self,aTableName]); + fTableName := aTableName; + fField := TObjectList.Create; + fFieldVariableIndex := -1; +end; + +procedure TSynTable.LoadFrom(var RD: TFileBufferReader); +var n, i: integer; + aTableName: RawUTF8; +begin + fField.Clear; + RD.Read(aTableName); + if not PropNameValid(pointer(aTableName)) then + RD.ErrorInvalidContent; + fTableName := aTableName; + n := RD.ReadVarUInt32; + if cardinal(n)>=MAX_SQLFIELDS then + RD.ErrorInvalidContent; + for i := 0 to n-1 do + fField.Add(TSynTableFieldProperties.CreateFrom(RD)); + AfterFieldModif; +end; + +destructor TSynTable.Destroy; +begin + fField.Free; + fAddedField.Free; + inherited; +end; + +function TSynTable.GetFieldCount: integer; +begin + if self=nil then + result := 0 else + result := fField.Count; +end; + +function TSynTable.GetFieldFromName(const aName: RawUTF8): TSynTableFieldProperties; +var i: integer; +begin + if self<>nil then + for i := 0 to fField.Count-1 do begin + result := TSynTableFieldProperties(fField.List[i]); + if IdemPropNameU(result.Name,aName) then + exit; + end; + result := nil; +end; + +function TSynTable.GetFieldIndexFromName(const aName: RawUTF8): integer; +begin + if self<>nil then + for result := 0 to fField.Count-1 do + if IdemPropNameU(TSynTableFieldProperties(fField.List[result]).Name,aName) then + exit; + result := -1; +end; + +function TSynTable.GetFieldIndexFromShortName(const aName: ShortString): integer; +begin + if self<>nil then + for result := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[result]) do + if IdemPropName(aName,pointer(Name),length(Name)) then + exit; + result := -1; +end; + +function TSynTable.GetFieldType(Index: integer): TSynTableFieldProperties; +begin + if (self=nil) or (cardinal(Index)>=cardinal(fField.Count)) then + result := nil else // avoid GPF + result := fField.List[Index]; +end; + +{$ifndef DELPHI5OROLDER} + +function TSynTable.CreateJSONWriter(JSON: TStream; Expand, withID: boolean; + const Fields: TSQLFieldBits): TJSONWriter; +begin + result := CreateJSONWriter(JSON,Expand,withID,FieldBitsToIndex(Fields,fField.Count)); +end; + +function TSynTable.CreateJSONWriter(JSON: TStream; Expand, withID: boolean; + const Fields: TSQLFieldIndexDynArray): TJSONWriter; +var i,nf,n: integer; +begin + if (self=nil) or ((Fields=nil) and not withID) then begin + result := nil; // no data to retrieve + exit; + end; + result := TJSONWriter.Create(JSON,Expand,withID,Fields); + // set col names + if withID then + n := 1 else + n := 0; + nf := length(Fields); + SetLength(result.ColNames,nf+n); + if withID then + result.ColNames[0] := 'ID'; + for i := 0 to nf-1 do + result.ColNames[i+n] := TSynTableFieldProperties(fField.List[Fields[i]]).Name; + result.AddColumns; // write or init field names for appropriate JSON Expand +end; + +procedure TSynTable.GetJSONValues(aID: integer; RecordBuffer: PUTF8Char; + W: TJSONWriter); +var i,n: integer; + buf: array[0..MAX_SQLFIELDS-1] of PUTF8Char; +begin + if (self=nil) or (RecordBuffer=nil) or (W=nil) then + exit; // avoid GPF + if W.Expand then begin + W.Add('{'); + if W.WithID then + W.AddString(W.ColNames[0]); + end; + if W.WithID then begin + W.Add(aID); + W.Add(','); + n := 1; + end else + n := 0; + for i := 0 to fField.Count-1 do begin + buf[i] := RecordBuffer; + inc(RecordBuffer,TSynTableFieldProperties(fField.List[i]).GetLength(RecordBuffer)); + end; + for i := 0 to length(W.Fields)-1 do begin + if W.Expand then begin + W.AddString(W.ColNames[n]); // '"'+ColNames[]+'":' + inc(n); + end; + TSynTableFieldProperties(fField.List[W.Fields[i]]).GetJSON(buf[i],W); + W.Add(','); + end; + W.CancelLastComma; // cancel last ',' + if W.Expand then + W.Add('}'); +end; + +function TSynTable.IterateJSONValues(Sender: TObject; Opaque: pointer; + ID: integer; Data: pointer; DataLen: integer): boolean; +var Statement: TSynTableStatement absolute Opaque; + F: TSynTableFieldProperties; + nWhere,fIndex: cardinal; +begin // note: we should have handled -2 (=COUNT) case already + nWhere := length(Statement.Where); + if (self=nil) or (Statement=nil) or (Data=nil) or + (Statement.Select=nil) or (nWhere>1) or + ((nWhere=1)and(Statement.Where[0].ValueSBF='')) then begin + result := false; + exit; + end; + result := true; + if nWhere=1 then begin // Where=nil -> all rows + fIndex := Statement.Where[0].Field; + if fIndex=SYNTABLESTATEMENTWHEREID then begin + if ID<>Statement.Where[0].ValueInteger then + exit; + end else begin + dec(fIndex); // 0 is ID, 1 for field # 0, 2 for field #1, and so on... + if fIndex0 then + exit; + end; + end; + end; + GetJSONValues(ID,Data,Statement.Writer); +end; + +{$endif DELPHI5OROLDER} + +function TSynTable.GetData(RecordBuffer: PUTF8Char; Field: TSynTableFieldProperties): pointer; +var i: integer; + PB: PByte; +begin + if Field.Offset>=0 then + result := RecordBuffer+Field.Offset else begin + result := RecordBuffer+fFieldVariableOffset; + for i := fFieldVariableIndex to Field.FieldNumber-1 do + if i in fFieldIsVarString then begin + // inlined result := GotoNextVarString(result); + if PByte(result)^<=$7f then + inc(PtrUInt(result),PByte(result)^+1) else begin + PB := result; + inc(PtrUInt(result),FromVarUInt32High(PB)+PtrUInt(PB)-PtrUInt(result)); + end; + end else + if not (i in fFieldIsExternal) then begin + // inlined result := GotoNextVarInt(result) + while PByte(result)^>$7f do inc(PtrUInt(result)); + inc(PtrUInt(result)); + end; + end; +end; + +procedure TSynTable.SaveTo(WR: TFileBufferWriter); +var i: Integer; +begin + WR.Write(fTableName); + WR.WriteVarUInt32(fField.Count); + for i := 0 to fField.Count-1 do + TSynTableFieldProperties(fField.List[i]).SaveTo(WR); +end; + +procedure TSynTable.AfterFieldModif; +var i, Offs: integer; +begin + PInt64(@fFieldIsVarString)^ := 0; + PInt64(@fFieldIsExternal)^ := 0; + fFieldVariableIndex := -1; + fDefaultRecordLength := 0; + fFieldHasUniqueIndexes := false; + Offs := 0; + for i := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[i]) do begin + FieldNumber := i; + {$ifndef SORTCOMPAREMETHOD} + SortCompare := FIELD_SORT[FieldType]; + {$endif} + Owner := self; + FieldSize := FIELD_FIXEDSIZE[FieldType]; + if FieldSize>=0 then begin + //assert(Offs>=0); + Offset := Offs; + inc(Offs,FieldSize); + inc(fDefaultRecordLength,FieldSize); + fDefaultFieldLength := FieldSize; + end else begin + if FieldSize=-3 then + Include(fFieldIsExternal,i) else begin + fDefaultFieldLength := 1; + inc(fDefaultRecordLength); + if FieldSize=-2 then + Include(fFieldIsVarString,i); + {$ifndef SORTCOMPAREMETHOD} + if (FieldType in [tftWinAnsi,tftUTF8]) and + (tfoCaseInsensitive in Options) then + SortCompare := SortIStr; // works for both WinAnsi and UTF-8 encodings + {$endif} + end; + // we need the Offset even for tftBlobExternal (FieldSize=-3) + if fFieldVariableIndex<0 then begin + fFieldVariableIndex := i; + fFieldVariableOffset := Offs; + Offs := -1; + end; + Offset := Offs; + dec(Offs); + end; + SetLength(fDefaultFieldData,fDefaultFieldLength); + FillcharFast(pointer(fDefaultFieldData)^,fDefaultFieldLength,0); + end; + SetLength(fDefaultRecordData,fDefaultRecordLength); + FillcharFast(pointer(fDefaultRecordData)^,fDefaultRecordLength,0); +end; + +procedure TSynTable.FieldIndexModify(aOldIndex, aNewIndex: integer; + aOldRecordData, aNewRecordData: pointer); +var F: integer; +begin + for F := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[F]) do + if tfoIndex in Options then + OrderedIndexUpdate(aOldIndex,aNewIndex,aOldRecordData,aNewRecordData); +end; + +procedure TSynTable.Filter(var RecordBuffer: TSBFString); +var Old, New: RawUTF8; + NewRecord: TSBFString; // UpdateFieldData update result in-place + F, i: integer; +begin + for F := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[F]) do + if Filters<>nil then begin + Old := GetRawUTF8(pointer(RecordBuffer)); + New := Old; + for i := 0 to Filters.Count-1 do + TSynFilter(Filters.List[i]).Process(F,New); + if Old<>New then begin + // value was changed -> store modified + UpdateFieldData(pointer(RecordBuffer),length(RecordBuffer),F, + NewRecord,SBFFromRawUTF8(New)); + RecordBuffer := NewRecord; + end; + end; +end; + +{$ifndef NOVARIANTS} +function TSynTable.Data(aID: integer; RecordBuffer: pointer; RecordBufferLen: Integer): Variant; +var data: TSynTableData absolute result; +begin + if SynTableVariantType=nil then + SynTableVariantType := SynRegisterCustomVariantType(TSynTableVariantType); + {$ifndef FPC} + if data.VType and VTYPE_STATIC<>0 then + {$endif} + VarClear(result); + data.VType := SynTableVariantType.VarType; + data.VID := aID; + data.VTable := self; + pointer(data.VValue) := nil; // avoid GPF + if RecordBuffer=nil then + data.VValue := DefaultRecordData else begin + if RecordBufferLen=0 then + RecordBufferLen := DataLength(RecordBuffer); + SetString(data.VValue,PAnsiChar(RecordBuffer),RecordBufferLen); + end; +end; +{$endif NOVARIANTS} + +function TSynTable.DataLength(RecordBuffer: pointer): integer; +var F: Integer; + PC: PUTF8Char; +begin + if (Self<>nil) and (RecordBuffer<>nil) then begin + PC := RecordBuffer; + for F := 0 to fField.Count-1 do + inc(PC,TSynTableFieldProperties(fField.List[F]).GetLength(PC)); + result := PC-RecordBuffer; + end else + result := 0; +end; + +function TSynTable.UpdateFieldEvent(Sender: TObject; Opaque: pointer; + ID, Index: integer; Data: pointer; DataLen: integer): boolean; +var Added: PUpdateFieldEvent absolute Opaque; + F, aSize: integer; +begin // in practice, this data processing is very fast (thanks to WR speed) + with Added^ do begin + result := Count1 shl 30 then + raise ETableDataException.CreateUTF8('%: File size too big (>1GB)',[self]) else + Offsets64[Count] := WR.TotalWritten; + IDs[Count] := ID; + NewIndexs[Index] := Count; + inc(Count); + end; +end; + +function TSynTable.UpdateFieldRecord(RecordBuffer: PUTF8Char; + var AvailableFields: TSQLFieldBits): TSBFString; +var Lens: array[0..MAX_SQLFIELDS-1] of Integer; + F, Len, TotalLen: integer; + P: PUTF8Char; + Dest: PByte; +begin + // retrieve all field buffer lengths, to speed up record content creation + TotalLen := 0; + P := RecordBuffer; + for F := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[F]) do + if F in AvailableFields then begin + Len := GetLength(P); + inc(P,Len); + inc(TotalLen,Len); + Lens[F] := Len; + end else + inc(TotalLen,fDefaultFieldLength); + // create new record content + P := RecordBuffer; + SetString(Result,nil,TotalLen); + Dest := pointer(Result); + for F := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[F]) do + if F in AvailableFields then begin + Len := Lens[F]; + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,Dest^,Len); + inc(P,Len); + inc(Dest,Len); + end else begin + FillcharFast(Dest^,fDefaultFieldLength,0); + inc(Dest,fDefaultFieldLength); + end; + //Assert(PtrUInt(Dest)-PtrUInt(result)=PtrUInt(TotalLen)); +end; + +function TSynTable.Validate(RecordBuffer: pointer; RecordIndex: integer): string; +var F: integer; +begin + result := ''; + for F := 0 to fField.Count-1 do + with TSynTableFieldProperties(fField.List[F]) do + if Validates<>nil then begin + result := Validate(RecordBuffer,RecordIndex); + if result<>'' then + exit; + end; +end; + + +{ TSynTableFieldProperties } + +constructor TSynTableFieldProperties.CreateFrom(var RD: TFileBufferReader); +begin + fOrderedIndexFindAdd := -1; + RD.Read(Name); + if not PropNameValid(pointer(Name)) then + RD.ErrorInvalidContent; + RD.Read(@FieldType,SizeOf(FieldType)); + RD.Read(@Options,SizeOf(Options)); + if (FieldType>high(FieldType)) then + RD.ErrorInvalidContent; + OrderedIndexCount := RD.ReadVarUInt32Array(OrderedIndex); + if OrderedIndexCount>0 then begin + if tfoIndex in Options then begin + //assert(OrderedIndexReverse=nil); + OrderedIndexReverseSet(-1); // compute whole OrderedIndexReverse[] array + end else + RD.ErrorInvalidContent; + end; + // we allow a void OrderedIndex[] array from disk +end; + +destructor TSynTableFieldProperties.Destroy; +begin + Filters.Free; + Validates.Free; + inherited; +end; + +function TSynTableFieldProperties.GetJSON(FieldBuffer: pointer; + W: TTextWriter): pointer; +var len: integer; + tmp: RawUTF8; +begin + case FieldType of + // fixed-sized field value + tftBoolean: + W.Add(PBoolean(FieldBuffer)^); + tftUInt8: + W.Add(PByte(FieldBuffer)^); + tftUInt16: + W.Add(PWord(FieldBuffer)^); + tftUInt24: + // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file + W.Add(PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16); + tftInt32: + W.Add(PInteger(FieldBuffer)^); + tftInt64: + W.Add(PInt64(FieldBuffer)^); + tftCurrency: + W.AddCurr64(PInt64(FieldBuffer)^); + tftDouble: + W.AddDouble(PDouble(FieldBuffer)^); + // some variable-size field value + tftVarUInt32: + W.Add(FromVarUInt32(PByte(FieldBuffer))); + tftVarInt32: + W.Add(FromVarInt32(PByte(FieldBuffer))); + tftVarUInt64: + W.AddQ(FromVarUInt64(PByte(FieldBuffer))); + tftVarInt64: + W.Add(FromVarInt64(PByte(FieldBuffer))); + // text storage - WinAnsi could use less space than UTF-8 + tftWinAnsi, tftUTF8: begin + W.Add('"'); + len := FromVarUInt32(PByte(FieldBuffer)); + if len>0 then + if FieldType=tftUTF8 then + W.AddJSONEscape(PAnsiChar(FieldBuffer),len) else begin + SetLength(tmp,len*3); // in-place decoding and appending + W.AddJSONEscape(pointer(tmp),WinAnsiBufferToUtf8(pointer(tmp),PAnsiChar(FieldBuffer),len)-pointer(tmp)); + end; + W.Add('"'); + result := PAnsiChar(FieldBuffer)+len; + exit; + end; + tftBlobInternal: begin + W.AddShort('"X'''); + len := FromVarUInt32(PByte(FieldBuffer)); + W.AddBinToHex(PByte(FieldBuffer),len); + W.Add('''','"'); + end; + tftBlobExternal: + ; // BLOB fields are not handled here, but must be directly accessed + end; + result := PAnsiChar(FieldBuffer)+FieldSize; // // tftWinAnsi,tftUTF8 already done +end; + +function TSynTableFieldProperties.GetLength(FieldBuffer: pointer): Integer; +var PB: PByte; +begin + if FieldSize>=0 then + result := FieldSize else + case FieldSize of + -1: begin // variable-length data + result := 0; + while PByteArray(FieldBuffer)^[result]>$7f do inc(result); + inc(result); + end; + -2: begin // tftWinAnsi, tftUTF8, tftBlobInternal records + result := PByte(FieldBuffer)^; + if result<=$7F then + inc(Result) else begin + PB := FieldBuffer; + result := FromVarUInt32High(PB)+PtrUInt(PB)-PtrUInt(FieldBuffer); + end; + end; + else + result := 0; // tftBlobExternal is not stored in FieldBuffer + end; +end; + +{$ifndef NOVARIANTS} +function TSynTableFieldProperties.GetVariant(FieldBuffer: pointer): Variant; +begin + GetVariant(FieldBuffer,result); +end; + +procedure TSynTableFieldProperties.GetVariant(FieldBuffer: pointer; var result: Variant); +var len: integer; + PB: PByte absolute FieldBuffer; + PA: PAnsiChar absolute FieldBuffer; + PU: PUTF8Char absolute FieldBuffer; + tmp: RawByteString; + {$ifndef UNICODE} + WS: WideString; + {$endif} +begin + case FieldType of + // fixed-sized field value + tftBoolean: + result := PBoolean(FieldBuffer)^; + tftUInt8: + result := PB^; + tftUInt16: + result := PWord(FieldBuffer)^; + tftUInt24: + // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file + result := PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16; + tftInt32: + result := PInteger(FieldBuffer)^; + tftInt64: + result := PInt64(FieldBuffer)^; + tftCurrency: + result := PCurrency(FieldBuffer)^; + tftDouble: + result := PDouble(FieldBuffer)^; + // some variable-size field value + tftVarUInt32: + result := FromVarUInt32(PB); + tftVarInt32: + result := FromVarInt32(PB); + tftVarUInt64: + result := FromVarUInt64(PB); + tftVarInt64: + result := FromVarInt64(PB); + // text storage - WinAnsi could use less space than UTF-8 + tftWinAnsi: begin + len := FromVarUInt32(PB); + if len>0 then + {$ifdef UNICODE} + result := WinAnsiToUnicodeString(PA,len) + {$else} + result := CurrentAnsiConvert.AnsiToAnsi(WinAnsiConvert,PA,len) + {$endif} else + result := ''; + end; + tftUTF8: begin + len := FromVarUInt32(PB); + if len>0 then + {$ifdef UNICODE} + result := UTF8DecodeToUnicodeString(PU,len) + {$else} begin + UTF8ToSynUnicode(PU,len,WS); + result := WS; + end + {$endif} else + result := ''; + end; + tftBlobInternal: begin + len := FromVarUInt32(PB); + SetString(tmp,PA,len); + result := tmp; // return internal BLOB content as string + end + else + result := ''; // tftBlobExternal fields e.g. must be directly accessed + end; +end; +{$endif} + +{$ifdef ISDELPHI20062007} + {$WARNINGS OFF} // circument Delphi 2007 false positive warning +{$endif} + +function TSynTableFieldProperties.GetValue(FieldBuffer: pointer): RawUTF8; +var len: integer; + PB: PByte absolute FieldBuffer; + PC: PAnsiChar absolute FieldBuffer; +begin + result := ''; + case FieldType of + // fixed-sized field value + tftBoolean: + JSONBoolean(PBoolean(FieldBuffer)^,result); + tftUInt8: + UInt32ToUtf8(PB^,result); + tftUInt16: + UInt32ToUtf8(PWord(FieldBuffer)^,result); + tftUInt24: + // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file + UInt32ToUtf8(PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16,result); + tftInt32: + Int32ToUtf8(PInteger(FieldBuffer)^,result); + tftInt64: + Int64ToUtf8(PInt64(FieldBuffer)^,result); + tftCurrency: + Curr64ToStr(PInt64(FieldBuffer)^,result); + tftDouble: + ExtendedToStr(PDouble(FieldBuffer)^,DOUBLE_PRECISION,result); + // some variable-size field value + tftVarUInt32: + UInt32ToUtf8(FromVarUInt32(PB),result); + tftVarInt32: + Int32ToUtf8(FromVarInt32(PB),result); + tftVarUInt64: + UInt64ToUtf8(FromVarUInt64(PB),result); + tftVarInt64: + Int64ToUtf8(FromVarInt64(PB),result); + // text storage - WinAnsi could use less space than UTF-8 + tftWinAnsi, tftUTF8, tftBlobInternal: begin + len := FromVarUInt32(PB); + if len>0 then + if FieldType<>tftWinAnsi then + SetString(result,PC,len) else + result := WinAnsiConvert.AnsiBufferToRawUTF8(PC,len); + end; + // tftBlobExternal fields e.g. must be directly accessed + end; +end; + +{$ifdef ISDELPHI20062007} + {$WARNINGS ON} // circument Delphi 2007 false positive warning +{$endif} + +procedure TSynTableFieldProperties.OrderedIndexReverseSet(aOrderedIndex: integer); +var nrev, ndx, n: PtrInt; +begin + n := length(OrderedIndex); + nrev := length(OrderedIndexReverse); + if nrev=0 then + if n=0 then + exit else begin + // void OrderedIndexReverse[] + nrev := MaxInteger(OrderedIndex,OrderedIndexCount,n)+1; + SetLength(OrderedIndexReverse,nrev); + FillcharFast(OrderedIndexReverse[0],nrev*4,255); // all to -1 + Reverse(OrderedIndex,OrderedIndexCount,pointer(OrderedIndexReverse)); + end; + if PtrUInt(aOrderedIndex)>=PtrUInt(OrderedIndexCount) then + exit; // e.g. CreateFrom() will call OrderedIndexReverseSet(-1) + if nrev=nrev then + SetLength(OrderedIndexReverse,ndx+256) else + OrderedIndexReverse[ndx] := aOrderedIndex; +end; + +procedure TSynTableFieldProperties.OrderedIndexSort(L, R: PtrInt); +var I, J, P: PtrInt; + TmpI, TmpJ: integer; +begin + if (L0 do dec(J); + end; + if I <= J then begin + if I < J then begin + TmpJ := OrderedIndex[J]; + TmpI := OrderedIndex[I]; + OrderedIndex[J] := TmpI; + OrderedIndex[I] := TmpJ; + // keep OrderedIndexReverse[OrderedIndex[i]]=i + OrderedIndexReverse[TmpJ] := I; + OrderedIndexReverse[TmpI] := J; + end; + if P = I then P := J else if P = J then P := I; + inc(I); dec(J); + end; + until I > J; + if J - L < R - I then begin // use recursion only for smaller range + if L < J then + OrderedIndexSort(L, J); + L := I; + end else begin + if I < R then + OrderedIndexSort(I, R); + R := J; + end; + until L >= R; +end; + +procedure TSynTableFieldProperties.OrderedIndexRefresh; +begin + if (self=nil) or not OrderedIndexNotSorted then + exit; // already sorted + OrderedIndexSort(0,OrderedIndexCount-1); + OrderedIndexNotSorted := false; +end; + +function TSynTableFieldProperties.OrderedIndexFind(Value: pointer): PtrInt; +var L,R: PtrInt; + cmp: PtrInt; +begin + if OrderedIndexNotSorted then + OrderedIndexRefresh; + L := 0; + R := OrderedIndexCount-1; + with Owner do + if (R>=0) and Assigned(GetRecordData) then + repeat + result := (L + R) shr 1; + cmp := SortCompare(GetData(GetRecordData(OrderedIndex[result],DataTemp1),self),Value); + if cmp=0 then + exit; + if cmp<0 then + L := result + 1 else + R := result - 1; + until (L > R); + result := -1 +end; + +function TSynTableFieldProperties.OrderedIndexFindAdd(Value: pointer): PtrInt; +var L,R,i: PtrInt; + cmp: PtrInt; +begin + if OrderedIndexNotSorted then + OrderedIndexRefresh; + R := OrderedIndexCount-1; + if R<0 then + result := 0 else + with Owner do begin + fOrderedIndexFindAdd := -1; + L := 0; + result := -1; // return -1 if found + repeat + i := (L + R) shr 1; + cmp := SortCompare(GetData(GetRecordData(OrderedIndex[i],DataTemp1),self),Value); + if cmp=0 then + exit; + if cmp<0 then + L := i + 1 else + R := i - 1; + until (L > R); + while (i>=0) and + (SortCompare(GetData(GetRecordData(OrderedIndex[i],DataTemp1),self),Value)>=0) do + dec(i); + result := i+1; // return the index where to insert + end; + fOrderedIndexFindAdd := result; // store inserting index for OrderedIndexUpdate +end; + +function TSynTableFieldProperties.OrderedIndexMatch(WhereSBFValue: pointer; + var MatchIndex: TIntegerDynArray; var MatchIndexCount: integer; Limit: Integer=0): Boolean; +var i, L,R: PtrInt; +begin + result := false; + if (self=nil) or (WhereSBFValue=nil) or not Assigned(Owner.GetRecordData) or + (OrderedIndex=nil) or not (tfoIndex in Options) then + exit; + i := OrderedIndexFind(WhereSBFValue); + if i<0 then + exit; // WHERE value not found + if (tfoUnique in Options) or (Limit=1) then begin + // unique index: direct fastest O(log(n)) binary search + AddSortedInteger(MatchIndex,MatchIndexCount,OrderedIndex[i]); + // AddSortedInteger() will fail if OrderedIndex[i] already exists + end else + with Owner do begin + // multiple index matches possible: add matching range + L := i; + repeat + dec(L); + until (L<0) or (SortCompare(GetData(GetRecordData( + OrderedIndex[L],DataTemp1),self),WhereSBFValue)<>0); + R := i; + repeat + inc(R); + until (R>=OrderedIndexCount) or + (SortCompare(GetData(GetRecordData(OrderedIndex[R],DataTemp1),self),WhereSBFValue)<>0); + if Limit=0 then + Limit := MaxInt; // no LIMIT set -> retrieve all rows + for i := L+1 to R-1 do begin + AddSortedInteger(MatchIndex,MatchIndexCount,OrderedIndex[i]); + dec(Limit); + if Limit=0 then + Break; // reach LIMIT upperbound result count + end; + end; + result := true; +end; + +function TSynTableFieldProperties.OrderedIndexUpdate(aOldIndex, aNewIndex: integer; + aOldRecordData, aNewRecordData: pointer): boolean; +var aOldIndexIndex: integer; +begin + result := false; + if (self=nil) or not Assigned(Owner.GetRecordData) then + exit; // avoid GPF + // update content + if aOldIndex<0 then + if aNewIndex<0 then begin + // both indexes equal -1 -> force sort + OrderedIndexSort(0,OrderedIndexCount-1); + OrderedIndexNotSorted := false; + end else begin + // added record + if tfoUnique in Options then begin + if fOrderedIndexFindAdd<0 then + raise ETableDataException.CreateUTF8( + '%.CheckConstraint call needed before %.OrderedIndexUpdate',[self,Name]); + OrderedIndexReverseSet(InsertInteger(OrderedIndex,OrderedIndexCount, + aNewIndex,fOrderedIndexFindAdd)); + end else begin + AddInteger(OrderedIndex,OrderedIndexCount,aNewIndex); + OrderedIndexReverseSet(OrderedIndexCount-1); + OrderedIndexNotSorted := true; // -> OrderedIndexSort() call on purpose + end; + end else begin + // aOldIndex>=0: update a value + // retrieve position in OrderedIndex[] to be deleted/updated + if OrderedIndexReverse=nil then + OrderedIndexReverseSet(0) else // do OrderedIndexReverse[OrderedIndex[i]] := i + {assert(aOldIndexnil) or (aOldIndex<>aNewIndex) then // not in-place update + with Owner do begin + if aOldRecordData=nil then + aOldRecordData := GetRecordData(aOldIndex,DataTemp1); + if aNewRecordData=nil then + aNewRecordData := GetRecordData(aNewIndex,DataTemp2); + if SortCompare(GetData(aOldRecordData,self),GetData(aNewRecordData,self))=0 then begin + // only sort if field content was modified -> MUCH faster in most case + result := true; + exit; + end; + end; + if tfoUnique in Options then begin + if fOrderedIndexFindAdd>=0 then begin + // we know which OrderedIndex[] has to be changed -> manual update + // - this is still a bottleneck in the current implementation, but + // I was not able to find out how to make it faster, and still + // being able to check unique field constraints without changing the + // OrderedIndex[] content from a simple list into e.g. a red-black + // tree: such a structure performs better, but uses much more memory + // and is to be implemented + // - it's still fast, faster than any DB AFAIK, around 500 updates + // per second with 1,000,000 records on a Core i7 + // - it's still faster to refresh OrderedIndex[] than iterating + // through all items to validate the unique constraint + DeleteInteger(OrderedIndex,OrderedIndexCount,aOldIndexIndex); + if fOrderedIndexFindAdd>aOldIndexIndex then + dec(fOrderedIndexFindAdd); + InsertInteger(OrderedIndex,OrderedIndexCount,aNewIndex,fOrderedIndexFindAdd); + Reverse(OrderedIndex,OrderedIndexCount,pointer(OrderedIndexReverse)); + end else + // slow full sort - with 1,000,000 items it's about 100 times slower + // (never called with common usage in SynBigTable unit) + OrderedIndexSort(0,OrderedIndexCount-1); + end else + OrderedIndexNotSorted := true; // will call OrderedIndexSort() on purpose + end; + end; + fOrderedIndexFindAdd := -1; // consume this value + result := true; +end; + +procedure TSynTableFieldProperties.SaveTo(WR: TFileBufferWriter); +begin + WR.Write(Name); + WR.Write(@FieldType,SizeOf(FieldType)); + WR.Write(@Options,SizeOf(Options)); + WR.WriteVarUInt32Array(OrderedIndex,OrderedIndexCount,wkVarUInt32); +end; + +function TSynTableFieldProperties.SBF(const Value: Int64): TSBFString; +var tmp: array[0..15] of AnsiChar; +begin + case FieldType of + tftInt32: begin // special version for handling negative values + PInteger(@tmp)^ := Value; + SetString(Result,tmp,SizeOf(Integer)); + end; + tftUInt8, tftUInt16, tftUInt24, tftInt64: + SetString(Result,PAnsiChar(@Value),FieldSize); + tftVarUInt32: + SetString(Result,tmp,PAnsiChar(ToVarUInt32(Value,@tmp))-tmp); + tftVarInt32: + SetString(Result,tmp,PAnsiChar(ToVarInt32(Value,@tmp))-tmp); + tftVarUInt64: + SetString(Result,tmp,PAnsiChar(ToVarUInt64(Value,@tmp))-tmp); + tftVarInt64: + SetString(Result,tmp,PAnsiChar(ToVarInt64(Value,@tmp))-tmp); + else + result := ''; + end; +end; + +function TSynTableFieldProperties.SBF(const Value: Integer): TSBFString; +var tmp: array[0..15] of AnsiChar; +begin + case FieldType of + tftUInt8, tftUInt16, tftUInt24, tftInt32: + SetString(Result,PAnsiChar(@Value),FieldSize); + tftInt64: begin // special version for handling negative values + PInt64(@tmp)^ := Value; + SetString(Result,tmp,SizeOf(Int64)); + end; + tftVarUInt32: + if Value<0 then // expect an unsigned integer + result := '' else + SetString(Result,tmp,PAnsiChar(ToVarUInt32(Value,@tmp))-tmp); + tftVarInt32: + SetString(Result,tmp,PAnsiChar(ToVarInt32(Value,@tmp))-tmp); + tftVarUInt64: + if cardinal(Value)>cardinal(maxInt) then + result := '' else // expect a 32 bit integer + SetString(Result,tmp,PAnsiChar(ToVarUInt64(Value,@tmp))-tmp); + tftVarInt64: + SetString(Result,tmp,PAnsiChar(ToVarInt64(Value,@tmp))-tmp); + else + result := ''; + end; +end; + +const + SBF_BOOL: array[boolean] of TSBFString = + (#0,#1); + +{$ifndef NOVARIANTS} +function TSynTableFieldProperties.SBF(const Value: Variant): TSBFString; +var V64: Int64; + VC: Currency absolute V64; + VD: Double absolute V64; +begin // VarIsOrdinal/VarIsFloat/VarIsStr are buggy -> use field type + case FieldType of + tftBoolean: + result := SBF_BOOL[boolean(Value)]; + tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64, + tftVarUInt32, tftVarInt32, tftVarUInt64, tftVarInt64: begin + if not VariantToInt64(Value,V64) then + V64 := 0; + result := SBF(V64); + end; + tftCurrency: begin + VC := Value; + SetString(result,PAnsiChar(@VC),SizeOf(VC)); + end; + tftDouble: begin + VD := Value; + SetString(result,PAnsiChar(@VD),SizeOf(VD)); + end; + tftWinAnsi: + ToSBFStr(WinAnsiConvert.UTF8ToAnsi(VariantToUTF8(Value)),result); + tftUTF8: + ToSBFStr(VariantToUTF8(Value),result); + else + result := ''; + end; + if result='' then + result := SBFDefault; +end; +{$endif} + +function TSynTableFieldProperties.SBF(const Value: Boolean): TSBFString; +begin + if FieldType<>tftBoolean then + result := '' else + result := SBF_BOOL[Value]; +end; + +function TSynTableFieldProperties.SBFCurr(const Value: Currency): TSBFString; +begin + if FieldType<>tftCurrency then + result := '' else + SetString(Result,PAnsiChar(@Value),SizeOf(Value)); +end; + +procedure ToSBFStr(const Value: RawByteString; out Result: TSBFString); +var tmp: array[0..15] of AnsiChar; + Len, Head: integer; +begin + if PtrUInt(Value)=0 then + Result := #0 else begin + Len := {$ifdef FPC}length(Value){$else}PInteger(PtrUInt(Value)-SizeOf(integer))^{$endif}; + Head := PAnsiChar(ToVarUInt32(Len,@tmp))-tmp; + SetLength(Result,Len+Head); + {$ifdef FPC}Move{$else}MoveFast{$endif}(tmp,PByteArray(Result)[0],Head); + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Value)^,PByteArray(Result)[Head],Len); + end; +end; + +function TSynTableFieldProperties.SBF(const Value: RawUTF8): TSBFString; +begin + case FieldType of + tftUTF8: + ToSBFStr(Value,Result); + tftWinAnsi: + ToSBFStr(Utf8ToWinAnsi(Value),Result); + else + result := ''; + end; +end; + +function TSynTableFieldProperties.SBF(Value: pointer; ValueLen: integer): TSBFString; +var tmp: array[0..15] of AnsiChar; + Head: integer; +begin + if FieldType<>tftBlobInternal then + result := '' else + if (Value=nil) or (ValueLen=0) then + result := #0 else begin // inlined ToSBFStr() code + Head := PAnsiChar(ToVarUInt32(ValueLen,@tmp))-tmp; + SetString(Result,nil,ValueLen+Head); + {$ifdef FPC}Move{$else}MoveFast{$endif}(tmp,PByteArray(Result)[0],Head); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Value^,PByteArray(Result)[Head],ValueLen); + end; +end; + +function TSynTableFieldProperties.SBFFloat(const Value: Double): TSBFString; +begin + if FieldType<>tftDouble then + result := '' else + SetString(Result,PAnsiChar(@Value),SizeOf(Value)); +end; + +function TSynTableFieldProperties.SBFFromRawUTF8(const aValue: RawUTF8): TSBFString; +var Curr: Currency; +begin + case FieldType of + tftBoolean: + if (SynCommons.GetInteger(pointer(aValue))<>0) or IdemPropNameU(aValue,'true') then + result := #1 else + result := #0; // store false by default + tftUInt8, tftUInt16, tftUInt24, tftInt32, tftVarInt32: + result := SBF(SynCommons.GetInteger(pointer(aValue))); + tftVarUInt32, tftInt64, tftVarUInt64, tftVarInt64: + result := SBF(SynCommons.GetInt64(pointer(aValue))); + tftCurrency: begin + PInt64(@Curr)^ := StrToCurr64(pointer(aValue)); + result := SBFCurr(Curr); + end; + tftDouble: + result := SBFFloat(GetExtended(pointer(aValue))); + // text storage - WinAnsi could use less space than UTF-8 + tftUTF8, tftWinAnsi: + result := SBF(aValue); + else + result := ''; // tftBlob* fields e.g. must be handled directly + end; +end; + +function TSynTableFieldProperties.GetInteger(RecordBuffer: pointer): Integer; +begin + if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then + result := 0 else begin + RecordBuffer := Owner.GetData(RecordBuffer,self); + case FieldType of + tftBoolean, tftUInt8: + result := PByte(RecordBuffer)^; + tftUInt16: + result := PWord(RecordBuffer)^; + tftUInt24: + // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file + result := PWord(RecordBuffer)^+integer(PByteArray(RecordBuffer)^[2])shl 16; + tftInt32: + result := PInteger(RecordBuffer)^; + tftInt64: + result := PInt64(RecordBuffer)^; + // some variable-size field value + tftVarUInt32: + result := FromVarUInt32(PByte(RecordBuffer)); + tftVarInt32: + result := FromVarInt32(PByte(RecordBuffer)); + tftVarUInt64: + result := FromVarUInt64(PByte(RecordBuffer)); + tftVarInt64: + result := FromVarInt64(PByte(RecordBuffer)); + else + result := 0; + end; + end; +end; + +function TSynTableFieldProperties.GetInt64(RecordBuffer: pointer): Int64; +var PB: PByte; +begin + if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then + result := 0 else begin + PB := Owner.GetData(RecordBuffer,self); + case FieldType of + tftInt64: + result := PInt64(PB)^; + tftVarUInt64: + result := FromVarUInt64(PB); + tftVarInt64: + result := FromVarInt64(PB); + else + result := GetInteger(RecordBuffer); + end; + end; +end; + +function TSynTableFieldProperties.GetBoolean(RecordBuffer: pointer): Boolean; +begin + result := boolean(GetInteger(RecordBuffer)); +end; + +function TSynTableFieldProperties.GetCurrency(RecordBuffer: pointer): Currency; +begin + if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then + result := 0 else + case FieldType of + tftCurrency: + result := PCurrency(Owner.GetData(RecordBuffer,self))^; + else + result := GetInt64(RecordBuffer); + end; +end; + +function TSynTableFieldProperties.GetDouble(RecordBuffer: pointer): Double; +begin + if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then + result := 0 else + case FieldType of + tftDouble: + result := PDouble(Owner.GetData(RecordBuffer,self))^; + else + result := GetInt64(RecordBuffer); + end; +end; + +function TSynTableFieldProperties.GetRawUTF8(RecordBuffer: pointer): RawUTF8; +begin + if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then + result := '' else begin + RecordBuffer := Owner.GetData(RecordBuffer,self); + if RecordBuffer<>nil then + result := GetValue(RecordBuffer) else // will do conversion to text + result := ''; + end; +end; + +function TSynTableFieldProperties.AddFilterOrValidate(aFilter: TSynFilterOrValidate): TSynFilterOrValidate; +procedure Add(var List: TObjectList); +begin + if List=nil then + List := TObjectList.Create; + List.Add(result); +end; +begin + result := aFilter; + if (self=nil) or (result=nil) then + result := nil else + if aFilter.InheritsFrom(TSynFilter) then + Add(Filters) else + if aFilter.InheritsFrom(TSynValidate) then + Add(Validates) else + result := nil; +end; + +function TSynTableFieldProperties.Validate(RecordBuffer: pointer; + RecordIndex: integer): string; +var i: integer; + Value: RawUTF8; + aValidate: TSynValidate; + aValidateTable: TSynValidateTable absolute aValidate; +begin + result := ''; + if (self=nil) or (Validates=nil) then + exit; + Value := GetRawUTF8(RecordBuffer); // TSynTableValidate needs RawUTF8 text + for i := 0 to Validates.Count-1 do begin + aValidate := Validates.List[i]; + if aValidate.InheritsFrom(TSynValidateTable) then begin + aValidateTable.ProcessField := self; + aValidateTable.ProcessRecordIndex := RecordIndex; + end; + if not aValidate.Process(FieldNumber,Value,result) then begin + if result='' then + // no custom message -> show a default message + result := format(sValidationFailed,[ + GetCaptionFromClass(aValidate.ClassType)]); + break; + end; + end; +end; + +{$ifdef SORTCOMPAREMETHOD} +function TSynTableFieldProperties.SortCompare(P1, P2: PUTF8Char): PtrInt; +var i, L: integer; +label minus,plus,zer; +begin + if P1<>P2 then + if P1<>nil then + if P2<>nil then + case FieldType of + tftBoolean, tftUInt8: + result := PByte(P1)^-PByte(P2)^; + tftUInt16: + result := PWord(P1)^-PWord(P2)^; + tftUInt24: + result := PtrInt(PWord(P1)^)+PtrInt(P1[2])shl 16 + -PtrInt(PWord(P2)^)-PtrInt(P2[2]) shl 16; + tftInt32: + result := PInteger(P1)^-PInteger(P2)^; + tftDouble: begin + PDouble(@SortCompareTmp)^ := PDouble(P1)^-PDouble(P2)^; + if PDouble(@SortCompareTmp)^<0 then + goto minus else + if PDouble(@SortCompareTmp)^>0 then + goto plus else + goto zer; + end; + tftVarUInt32: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + result := FromVarUInt32(PB1)-FromVarUInt32(PB2); + end; + tftVarInt32: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + result := FromVarInt32(PB1)-FromVarInt32(PB2); + end; + {$ifdef CPUX86} // circumvent comparison slowness (and QWord bug) + tftInt64, tftCurrency: + result := SortInt64(PInt64(P1)^,PInt64(P2)^); + tftVarUInt64: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + result := SortQWord(FromVarUInt64(PB1),FromVarUInt64(PB2)); + end; + tftVarInt64: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + result := SortInt64(FromVarInt64(PB1),FromVarInt64(PB2)); + end; + {$else} + {$ifdef CPU64} // PtrInt = Int64 + tftInt64, tftCurrency: + result := PInt64(P1)^-PInt64(P2)^; + tftVarUInt64: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + result := FromVarUInt64(PB1)-FromVarUInt64(PB2); + end; + tftVarInt64: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + result := FromVarInt64(PB1)-FromVarInt64(PB2); + end; + {$else} + tftInt64, tftCurrency: begin + PInt64(@SortCompareTmp)^ := PInt64(P1)^-PInt64(P2)^; + if PInt64(@SortCompareTmp)^<0 then + goto minus else + if PInt64(@SortCompareTmp)^>0 then + goto plus else + goto zer; + end; + tftVarUInt64: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + PInt64(@SortCompareTmp)^ := FromVarUInt64(PB1)-FromVarUInt64(PB2); + if PInt64(@SortCompareTmp)^<0 then + goto minus else + if PInt64(@SortCompareTmp)^>0 then + goto plus else + goto zer; + end; + tftVarInt64: + with SortCompareTmp do begin + PB1 := Pointer(P1); + PB2 := Pointer(P2); + PInt64(@SortCompareTmp)^ := FromVarInt64(PB1)-FromVarInt64(PB2); + if PInt64(@SortCompareTmp)^<0 then + goto minus else + if PInt64(@SortCompareTmp)^>0 then + goto plus else + goto zer; + end; + {$endif} + {$endif} + tftWinAnsi, tftUTF8, tftBlobInternal: + begin + with SortCompareTmp do begin + if PtrInt(P1^)<=$7F then begin + L1 := PtrInt(P1^); + inc(P1); + end else begin + PB1 := pointer(P1); + L1 := FromVarUInt32High(PB1); + P1 := pointer(PB1); + end; + if PtrInt(P2^)<=$7F then begin + L2 := PtrInt(P2^); + inc(P2); + end else begin + PB2 := pointer(P2); + L2 := FromVarUInt32High(PB2); + P2 := pointer(PB2); + end; + end; + with SortCompareTmp do begin + L := L1; + if L2>L then + L := L2; + end; + if tfoCaseInsensitive in Options then begin + i := 0; + while i0 then + exit else + inc(i); + end; + end else begin + i := 0; + while i0 then + exit else + inc(i); + end; + end; + with SortCompareTmp do + result := L1-L2; + end; + else + goto zer; + end else +plus: result := 1 else // P2=nil +minus:result := -1 else // P1=nil +zer:result := 0; // P1=P2 +end; +{$endif} + +function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char; + Value: Int64; Oper: TCompareOperator): boolean; +var V: Int64; + PB: PByte absolute SBF; +begin + result := true; + if PB<>nil then + repeat + case FieldType of + tftBoolean, tftUInt8: + V := PB^; + tftUInt16: + V := PWord(PB)^; + tftUInt24: + // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file + V := PWord(PB)^+integer(PByteArray(PB)^[2])shl 16; + tftInt32: + V := PInteger(PB)^; + tftInt64: + V := PInt64(PB)^; + // some variable-size field value + tftVarUInt32: + V := FromVarUInt32(PB); + tftVarInt32: + V := FromVarInt32(PB); + tftVarUInt64: + V := FromVarUInt64(PB); + tftVarInt64: + V := FromVarInt64(PB); + else V := 0; // makes compiler happy + end; + case Oper of + soEqualTo: if V=Value then exit; + soNotEqualTo: if V<>Value then exit; + soLessThan: if VValue then exit; + soGreaterThanOrEqualTo: if V>=Value then exit; + else break; + end; + // not found: go to next value + if SBFEnd=nil then + break; // only one value to be checked + if FIELD_FIXEDSIZE[FieldType]>0 then + inc(SBF,FIELD_FIXEDSIZE[FieldType]); // FromVar*() already updated PB/SBF + until SBF>=SBFEnd; + result := false; // not found +end; + +function CompareOperator(SBF, SBFEnd: PUTF8Char; + Value: double; Oper: TCompareOperator): boolean; +begin + result := true; + if SBF<>nil then + repeat + case Oper of + soEqualTo: if PDouble(SBF)^=Value then exit; + soNotEqualTo: if PDouble(SBF)^<>Value then exit; + soLessThan: if PDouble(SBF)^Value then exit; + soGreaterThanOrEqualTo: if PDouble(SBF)^>=Value then exit; + else break; + end; + // not found: go to next value + if SBFEnd=nil then + break; // only one value to be checked + inc(SBF,SizeOf(Value)); + until SBF>=SBFEnd; + result := false; // not found +end; + +function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char; + Value: PUTF8Char; ValueLen: integer; Oper: TCompareOperator; + CaseSensitive: boolean): boolean; overload; +var L, Cmp: PtrInt; + PB: PByte; + tmp: array[byte] of AnsiChar; +begin + result := true; + if SBF<>nil then + repeat + // get length of text in the SBF encoded buffer + if integer(SBF^)<=$7f then begin + L := integer(SBF^); + inc(SBF); + end else begin + PB := Pointer(SBF); + L := FromVarUInt32(PB); + SBF := pointer(PB); + end; + // perform comparison: returns nil on match + case Oper of + soEqualTo..soGreaterThanOrEqualTo: begin + Cmp := L-ValueLen; + if Cmp<0 then + L := ValueLen; + if CaseSensitive then + Cmp := StrCompL(SBF,Value,L,Cmp) else + Cmp := StrCompIL(SBF,Value,L,Cmp); + case Oper of + soEqualTo: if Cmp=0 then exit; + soNotEqualTo: if Cmp<>0 then exit; + soLessThan: if Cmp<0 then exit; + soLessThanOrEqualTo: if Cmp<=0 then exit; + soGreaterThan: if Cmp>0 then exit; + soGreaterThanOrEqualTo: if Cmp>=0 then exit; + end; + end; + soBeginWith: + if ValueLen>=L then + if CaseSensitive then begin + if StrCompL(SBF,Value,ValueLen,0)=0 then + exit; + end else + if StrCompIL(SBF,Value,ValueLen,0)=0 then + exit; + soContains: begin + dec(L,ValueLen); + while L>=0 do begin + while (L>=0) and not(byte(SBF^) in IsWord) do begin + dec(L); + inc(SBF); + end; // begin of next word reached + if L<0 then + Break; // not enough chars to contain the Value + if CaseSensitive then begin + if StrCompL(SBF,Value,ValueLen,0)=0 then + exit; + end else + if StrCompIL(SBF,Value,ValueLen,0)=0 then + exit; + while (L>=0) and (byte(SBF^) in IsWord) do begin + dec(L); + inc(SBF); + end; // end of word reached + end; + if SBFEnd=nil then + break; // only one value to be checked + inc(SBF,ValueLen); // custom inc(SBF,L); + if SBFhigh(tmp) then + Cmp := high(tmp) else + Cmp := L; + tmp[Cmp] := #0; // TSynSoundEx expect the buffer to be #0 terminated + {$ifdef FPC}Move{$else}MoveFast{$endif}(SBF^,tmp,Cmp); + case FieldType of + tftWinAnsi: + if PSynSoundEx(Value)^.Ansi(tmp) then + exit; + tftUTF8: + if PSynSoundEx(Value)^.UTF8(tmp) then + exit; + else break; + end; + end; + else break; + end; + // no match -> go to the end of the SBF buffer + if SBFEnd=nil then + exit; // only one value to be checked + inc(SBF,L); + if SBF>=SBFEnd then + break; + until false; +end; + + +{ TSynValidateTableUniqueField } + +function TSynValidateTableUniqueField.Process(aFieldIndex: integer; + const Value: RawUTF8; var ErrorMsg: string): boolean; +var S: TSBFString; +begin + result := false; + if (self=nil) or (Value='') or (ProcessField=nil) then + exit; // void field can't be unique + if not (tfoIndex in ProcessField.Options) then + exit; // index should be always created by TSynTable.AfterFieldModif + S := ProcessField.SBFFromRawUTF8(Value); + if S='' then + exit; // void field can't be unique + if ProcessField.OrderedIndexFindAdd(Pointer(S))>=0 then + // there is some place to insert the Value -> not existing yet -> OK + result := true else begin + // RecordIndex=-1 in case of adding, or the physical index of the updated record + if (ProcessRecordIndex>=0) and + (ProcessField.OrderedIndex[ProcessField.OrderedIndexFind(Pointer(S))]= + ProcessRecordIndex) then + // allow update of the record + result := true else + // found a dupplicated value + ErrorMsg := sValidationFieldDuplicate; + end; +end; + + +{ TSynTableStatement } + +const + NULL_UPP = ord('N')+ord('U')shl 8+ord('L')shl 16+ord('L')shl 24; + +constructor TSynTableStatement.Create(const SQL: RawUTF8; + GetFieldIndex: TSynTableFieldIndex; SimpleFieldsBits: TSQLFieldBits; + FieldProp: TSynTableFieldProperties); +var Prop, whereBefore: RawUTF8; + P, B: PUTF8Char; + ndx,err,len,selectCount,whereCount: integer; + whereWithOR,whereNotClause: boolean; + +function GetPropIndex: integer; +begin + if not GetNextFieldProp(P,Prop) then + result := -1 else + if IsRowID(pointer(Prop)) then + result := 0 else begin // 0 = ID field + result := GetFieldIndex(Prop); + if result>=0 then // -1 = no valid field name + inc(result); // otherwise: PropertyIndex+1 + end; +end; +function SetFields: boolean; +var select: TSynTableStatementSelect; +begin + result := false; + FillcharFast(select,SizeOf(select),0); + select.Field := GetPropIndex; // 0 = ID, otherwise PropertyIndex+1 + if select.Field<0 then begin + if P^<>'(' then // Field not found -> try function(field) + exit; + P := GotoNextNotSpace(P+1); + select.FunctionName := Prop; + inc(fSelectFunctionCount); + if IdemPropNameU(Prop,'COUNT') and (P^='*') then begin + select.Field := 0; // count(*) -> count(ID) + select.FunctionKnown := funcCountStar; + P := GotoNextNotSpace(P+1); + end else begin + if IdemPropNameU(Prop,'DISTINCT') then + select.FunctionKnown := funcDistinct else + if IdemPropNameU(Prop,'MAX') then + select.FunctionKnown := funcMax; + select.Field := GetPropIndex; + if select.Field<0 then + exit; + end; + if P^<>')' then + exit; + P := GotoNextNotSpace(P+1); + end; + if P^ in ['+','-'] then begin + select.ToBeAdded := GetNextItemInteger(P,' '); + if select.ToBeAdded=0 then + exit; + P := GotoNextNotSpace(P); + end; + if IdemPChar(P,'AS ') then begin + inc(P,3); + if not GetNextFieldProp(P,select.Alias) then + exit; + end; + SetLength(fSelect,selectCount+1); + fSelect[selectCount] := select; + inc(selectCount); + result := true; +end; +function GetWhereValue(var Where: TSynTableStatementWhere): boolean; +var B: PUTF8Char; +begin + result := false; + P := GotoNextNotSpace(P); + Where.ValueSQL := P; + if PWord(P)^=ord(':')+ord('(') shl 8 then + inc(P,2); // ignore :(...): parameter (no prepared statements here) + if P^ in ['''','"'] then begin + // SQL String statement + P := UnQuoteSQLStringVar(P,Where.Value); + if P=nil then + exit; // end of string before end quote -> incorrect + {$ifndef NOVARIANTS} + RawUTF8ToVariant(Where.Value,Where.ValueVariant); + {$endif} + if FieldProp<>nil then + // create a SBF formatted version of the WHERE value + Where.ValueSBF := FieldProp.SBFFromRawUTF8(Where.Value); + end else + if (PInteger(P)^ and $DFDFDFDF=NULL_UPP) and (P[4] in [#0..' ',';']) then begin + // NULL statement + Where.Value := NULL_STR_VAR; // not void + {$ifndef NOVARIANTS} + SetVariantNull(Where.ValueVariant); + {$endif} + end else begin + // numeric statement or 'true' or 'false' (OK for NormalizeValue) + B := P; + repeat + inc(P); + until P^ in [#0..' ',';',')',',']; + SetString(Where.Value,B,P-B); + {$ifndef NOVARIANTS} + Where.ValueVariant := VariantLoadJSON(Where.Value); + {$endif} + Where.ValueInteger := GetInteger(pointer(Where.Value),err); + if FieldProp<>nil then + if Where.Value<>'?' then + if (FieldProp.FieldType in FIELD_INTEGER) and (err<>0) then + // we expect a true INTEGER value here + Where.Value := '' else + // create a SBF formatted version of the WHERE value + Where.ValueSBF := FieldProp.SBFFromRawUTF8(Where.Value); + end; + if PWord(P)^=ord(')')+ord(':')shl 8 then + inc(P,2); // ignore :(...): parameter + Where.ValueSQLLen := P-Where.ValueSQL; + P := GotoNextNotSpace(P); + if (P^=')') and (Where.FunctionName='') then begin + B := P; + repeat + inc(P); + until not (P^ in [#1..' ',')']); + while P[-1]=' ' do dec(P); // trim right space + SetString(Where.ParenthesisAfter,B,P-B); + P := GotoNextNotSpace(P); + end; + result := true; +end; +{$ifndef NOVARIANTS} +function GetWhereValues(var Where: TSynTableStatementWhere): boolean; +var v: TSynTableStatementWhereDynArray; + n, w: integer; + tmp: RawUTF8; +begin + result := false; + if Where.ValueSQLLen<=2 then + exit; + SetString(tmp,PAnsiChar(Where.ValueSQL)+1,Where.ValueSQLLen-2); + P := pointer(tmp); // parse again the IN (...,...,... ) expression + n := 0; + try + repeat + if n=length(v) then + SetLength(v,NextGrow(n)); + if not GetWhereValue(v[n]) then + exit; + inc(n); + if P^=#0 then + break; + if P^<>',' then + exit; + inc(P); + until false; + finally + P := Where.ValueSQL+Where.ValueSQLLen; // continue parsing as usual + end; + with TDocVariantData(Where.ValueVariant) do begin + InitFast(n,dvArray); + for w := 0 to n-1 do + AddItem(v[w].ValueVariant); + Where.Value := ToJSON; + end; + result := true; +end; +{$endif} +function GetWhereExpression(FieldIndex: integer; var Where: TSynTableStatementWhere): boolean; +begin + result := false; + Where.ParenthesisBefore := whereBefore; + Where.JoinedOR := whereWithOR; + Where.NotClause := whereNotClause; + Where.Field := FieldIndex; // 0 = ID, otherwise PropertyIndex+1 + case P^ of + '=': Where.Operator := opEqualTo; + '>': if P[1]='=' then begin + inc(P); + Where.Operator := opGreaterThanOrEqualTo; + end else + Where.Operator := opGreaterThan; + '<': case P[1] of + '=': begin + inc(P); + Where.Operator := opLessThanOrEqualTo; + end; + '>': begin + inc(P); + Where.Operator := opNotEqualTo; + end; + else + Where.Operator := opLessThan; + end; + 'i','I': + case P[1] of + 's','S': begin + P := GotoNextNotSpace(P+2); + if IdemPChar(P,'NULL') then begin + Where.Value := NULL_STR_VAR; + Where.Operator := opIsNull; + Where.ValueSQL := P; + Where.ValueSQLLen := 4; + {$ifndef NOVARIANTS} + TVarData(Where.ValueVariant).VType := varNull; + {$endif} + inc(P,4); + result := true; + end else + if IdemPChar(P,'NOT NULL') then begin + Where.Value := 'not null'; + Where.Operator := opIsNotNull; + Where.ValueSQL := P; + Where.ValueSQLLen := 8; + {$ifndef NOVARIANTS} + TVarData(Where.ValueVariant).VType := varNull; + {$endif} + inc(P,8); + result := true; // leave ValueVariant=unassigned + end; + exit; + end; + {$ifndef NOVARIANTS} + 'n','N': begin + Where.Operator := opIn; + P := GotoNextNotSpace(P+2); + if P^<>'(' then + exit; // incorrect SQL statement + B := P; // get the IN() clause as JSON + inc(P); + while (P^<>')') or (P[1]=':') do // handle :(...): within the clause + if P^=#0 then + exit else + inc(P); + inc(P); + SetString(Where.Value,PAnsiChar(B),P-B); + Where.ValueSQL := B; + Where.ValueSQLLen := P-B; + result := GetWhereValues(Where); + exit; + end; + {$endif} + end; // 'i','I': + 'l','L': + if IdemPChar(P+1,'IKE') then begin + inc(P,3); + Where.Operator := opLike; + end else + exit; + else exit; // unknown operator + end; + // we got 'WHERE FieldName operator ' -> handle value + inc(P); + result := GetWhereValue(Where); +end; + +label lim,lim2; +begin + P := pointer(SQL); + if (P=nil) or (self=nil) then + exit; // avoid GPF + P := GotoNextNotSpace(P); // trim left + if not IdemPChar(P,'SELECT ') then + exit else // handle only SELECT statement + inc(P,7); + // 1. get SELECT clause: set bits in Fields from CSV field IDs in SQL + selectCount := 0; + P := GotoNextNotSpace(P); // trim left + if P^=#0 then + exit; // no SQL statement + if P^='*' then begin // all simple (not TSQLRawBlob/TSQLRecordMany) fields + inc(P); + len := GetBitsCount(SimpleFieldsBits,MAX_SQLFIELDS)+1; + SetLength(fSelect,len); + selectCount := 1; // Select[0].Field := 0 -> ID + for ndx := 0 to MAX_SQLFIELDS-1 do + if ndx in SimpleFieldsBits then begin + fSelect[selectCount].Field := ndx+1; + inc(selectCount); + if selectCount=len then + break; + end; + GetNextFieldProp(P,Prop); + end else + if not SetFields then + exit else // we need at least one field name + if P^<>',' then + GetNextFieldProp(P,Prop) else + repeat + while P^ in [',',#1..' '] do inc(P); // trim left + until not SetFields; // add other CSV field names + // 2. get FROM clause + if not IdemPropNameU(Prop,'FROM') then exit; // incorrect SQL statement + GetNextFieldProp(P,Prop); + fTableName := Prop; + // 3. get WHERE clause + whereCount := 0; + whereWithOR := false; + whereNotClause := false; + whereBefore := ''; + GetNextFieldProp(P,Prop); + if IdemPropNameU(Prop,'WHERE') then begin + repeat + B := P; + if P^='(' then begin + fWhereHasParenthesis := true; + repeat + inc(P); + until not (P^ in [#1..' ','(']); + while P[-1]=' ' do dec(P); // trim right space + SetString(whereBefore,B,P-B); + B := P; + end; + ndx := GetPropIndex; + if ndx<0 then begin + if IdemPropNameU(Prop,'NOT') then begin + whereNotClause := true; + continue; + end; + if P^='(' then begin + inc(P); + SetLength(fWhere,whereCount+1); + with fWhere[whereCount] do begin + ParenthesisBefore := whereBefore; + JoinedOR := whereWithOR; + NotClause := whereNotClause; + FunctionName := UpperCase(Prop); + // Byte/Word/Integer/Cardinal/Int64/CurrencyDynArrayContains(BlobField,I64) + len := length(Prop); + if (len>16) and + IdemPropName('DynArrayContains',PUTF8Char(@PByteArray(Prop)[len-16]),16) then + Operator := opContains else + Operator := opFunction; + B := P; + Field := GetPropIndex; + if Field<0 then + P := B else + if P^<>',' then + break else + P := GotoNextNotSpace(P+1); + if (P^=')') or + (GetWhereValue(fWhere[whereCount]) and (P^=')')) then begin + inc(P); + break; + end; + end; + end; + P := B; + break; + end; + SetLength(fWhere,whereCount+1); + if not GetWhereExpression(ndx,fWhere[whereCount]) then + exit; // invalid SQL statement + inc(whereCount); + GetNextFieldProp(P,Prop); + if IdemPropNameU(Prop,'OR') then + whereWithOR := true else + if IdemPropNameU(Prop,'AND') then + whereWithOR := false else + goto lim2; + whereNotClause := false; + whereBefore := ''; + until false; + // 4. get optional LIMIT/OFFSET/ORDER clause +lim:P := GotoNextNotSpace(P); + while (P<>nil) and not(P^ in [#0,';']) do begin + GetNextFieldProp(P,Prop); +lim2: if IdemPropNameU(Prop,'LIMIT') then + fLimit := GetNextItemCardinal(P,' ') else + if IdemPropNameU(Prop,'OFFSET') then + fOffset := GetNextItemCardinal(P,' ') else + if IdemPropNameU(Prop,'ORDER') then begin + GetNextFieldProp(P,Prop); + if IdemPropNameU(Prop,'BY') then begin + repeat + ndx := GetPropIndex; // 0 = ID, otherwise PropertyIndex+1 + if ndx<0 then + exit; // incorrect SQL statement + AddFieldIndex(fOrderByField,ndx); + if P^<>',' then begin // check ORDER BY ... ASC/DESC + B := P; + if GetNextFieldProp(P,Prop) then + if IdemPropNameU(Prop,'DESC') then + fOrderByDesc := true else + if not IdemPropNameU(Prop,'ASC') then + P := B; + break; + end; + P := GotoNextNotSpace(P+1); + until P^ in [#0,';']; + end else + exit; // incorrect SQL statement + end else + if IdemPropNameU(Prop,'GROUP') then begin + GetNextFieldProp(P,Prop); + if IdemPropNameU(Prop,'BY') then begin + repeat + ndx := GetPropIndex; // 0 = ID, otherwise PropertyIndex+1 + if ndx<0 then + exit; // incorrect SQL statement + AddFieldIndex(fGroupByField,ndx); + if P^<>',' then + break; + P := GotoNextNotSpace(P+1); + until P^ in [#0,';']; + end else + exit; // incorrect SQL statement + end else + if Prop<>'' then + exit else // incorrect SQL statement + break; // reached the end of the statement + end; + end else + if Prop<>'' then + goto lim2; // handle LIMIT OFFSET ORDER + fSQLStatement := SQL; // make a private copy e.g. for Where[].ValueSQL +end; + +procedure TSynTableStatement.SelectFieldBits(var Fields: TSQLFieldBits; var withID: boolean); +var i: integer; +begin + FillcharFast(Fields,SizeOf(Fields),0); + withID := false; + for i := 0 to Length(Select)-1 do + if Select[i].Field=0 then + withID := true else + include(Fields,Select[i].Field-1); +end; + + +{$ifndef DELPHI5OROLDER} + +{ TSynTableData } + +procedure TSynTableData.CheckVTableInitialized; +begin + if VTable=nil then + raise ETableDataException.Create('TSynTableData non initialized'); +end; + +{$ifndef NOVARIANTS} + +function TSynTableData.GetFieldValue(const FieldName: RawUTF8): Variant; +begin + GetFieldVariant(FieldName,result); +end; + +procedure TSynTableData.GetFieldVariant(const FieldName: RawUTF8; var result: Variant); +var aField: TSynTableFieldProperties; +begin + if IsRowID(Pointer(FieldName)) then + result := VID else begin + CheckVTableInitialized; + aField := VTable.FieldFromName[FieldName]; + if aField=nil then + raise ETableDataException.CreateUTF8('Unknown % property',[FieldName]) else + aField.GetVariant(VTable.GetData(pointer(VValue),aField),result); + end; +end; + +function TSynTableData.GetFieldValue(aField: TSynTableFieldProperties): Variant; +begin + CheckVTableInitialized; + aField.GetVariant(VTable.GetData(pointer(VValue),aField),result); +end; + +{$endif NOVARIANTS} + +procedure TSynTableData.FilterSBFValue; +begin + CheckVTableInitialized; + VTable.Filter(VValue); +end; + +function TSynTableData.GetFieldSBFValue(aField: TSynTableFieldProperties): TSBFString; +var FieldBuffer: PAnsiChar; +begin + CheckVTableInitialized; + FieldBuffer := VTable.GetData(pointer(VValue),aField); + SetString(Result,FieldBuffer,aField.GetLength(FieldBuffer)); +end; + +procedure TSynTableData.Init(aTable: TSynTable; aID: Integer); +begin + VTable := aTable; + VID := aID; + VValue := VTable.DefaultRecordData; + {$ifdef UNICODE}FillcharFast(Filler,SizeOf(Filler),0);{$endif} +end; + +procedure TSynTableData.Init(aTable: TSynTable; aID: Integer; + RecordBuffer: pointer; RecordBufferLen: integer); +begin + VTable := aTable; + if (RecordBufferLen=0) or (RecordBuffer=nil) then begin + VID := 0; + VValue := VTable.DefaultRecordData; + end else begin + VID := aID; + SetString(VValue,PAnsiChar(RecordBuffer),RecordBufferLen); + end; +end; + +{$ifndef NOVARIANTS} +procedure TSynTableData.SetFieldValue(const FieldName: RawUTF8; + const Value: Variant); +var F: TSynTableFieldProperties; +begin + CheckVTableInitialized; + if IsRowID(Pointer(FieldName)) then + VID := Value else begin + F := VTable.FieldFromName[FieldName]; + if F=nil then + raise ETableDataException.CreateUTF8('Unknown % property',[FieldName]) else + SetFieldValue(F,Value); + end; +end; + +procedure TSynTableData.SetFieldValue(aField: TSynTableFieldProperties; const Value: Variant); +begin + SetFieldSBFValue(aField,aField.SBF(Value)); +end; +{$endif} + +procedure TSynTableData.SetFieldSBFValue(aField: TSynTableFieldProperties; + const Value: TSBFString); +var NewValue: TSBFString; +begin + CheckVTableInitialized; + if (aField.FieldSize>0) and (VValue<>'') then begin + // fixed size content: fast in-place update + {$ifdef FPC}Move{$else}MoveFast{$endif}(pointer(Value)^,VValue[aField.Offset+1],aField.FieldSize) + // VValue[F.Offset+1] above will call UniqueString(VValue), even under FPC + end else begin + // variable-length update + VTable.UpdateFieldData(pointer(VValue),length(VValue), + aField.FieldNumber,NewValue,Value); + VValue := NewValue; + end; +end; + +function TSynTableData.ValidateSBFValue(RecordIndex: integer): string; +begin + CheckVTableInitialized; + Result := VTable.Validate(Pointer(VValue),RecordIndex); +end; + +{$endif DELPHI5OROLDER} + + +{ ************ filtering and validation classes and functions *************** } + +function IsValidIP4Address(P: PUTF8Char): boolean; +var ndot: PtrInt; + V: PtrUInt; +begin + result := false; + if (P=nil) or not (P^ in ['0'..'9']) then + exit; + V := 0; + ndot := 0; + repeat + case P^ of + #0: break; + '.': if (P[-1]='.') or (V>255) then + exit else begin + inc(ndot); + V := 0; + end; + '0'..'9': V := (V*10)+ord(P^)-48; + else exit; + end; + inc(P); + until false; + if (ndot=3) and (V<=255) and (P[-1]<>'.') then + result := true; +end; + +function IsValidEmail(P: PUTF8Char): boolean; +// Initial Author: Ernesto D'Spirito - UTF-8 version by AB +// http://www.howtodothings.com/computers/a1169-validating-email-addresses-in-delphi.html +const + // Valid characters in an "atom" + atom_chars: TSynAnsicharSet = [#33..#255] - + ['(', ')', '<', '>', '@', ',', ';', ':', '\', '/', '"', '.', '[', ']', #127]; + // Valid characters in a "quoted-string" + quoted_string_chars: TSynAnsicharSet = [#0..#255] - ['"', #13, '\']; + // Valid characters in a subdomain + letters_digits: TSynAnsicharSet = ['0'..'9', 'A'..'Z', 'a'..'z']; +type + States = (STATE_BEGIN, STATE_ATOM, STATE_QTEXT, STATE_QCHAR, + STATE_QUOTE, STATE_LOCAL_PERIOD, STATE_EXPECTING_SUBDOMAIN, + STATE_SUBDOMAIN, STATE_HYPHEN); +var + State: States; + subdomains: integer; + c: AnsiChar; + ch: PtrInt; +begin + State := STATE_BEGIN; + subdomains := 1; + if P<>nil then + repeat + ch := ord(P^); + if ch and $80=0 then + inc(P) else + ch := GetHighUTF8UCS4(P); + if (ch<=255) and (WinAnsiConvert.AnsiToWide[ch]<=255) then + // convert into WinAnsi char + c := AnsiChar(ch) else + // invalid char + c := #127; + case State of + STATE_BEGIN: + if c in atom_chars then + State := STATE_ATOM else + if c='"' then + State := STATE_QTEXT else + break; + STATE_ATOM: + if c='@' then + State := STATE_EXPECTING_SUBDOMAIN else + if c='.' then + State := STATE_LOCAL_PERIOD else + if not (c in atom_chars) then + break; + STATE_QTEXT: + if c='\' then + State := STATE_QCHAR else + if c='"' then + State := STATE_QUOTE else + if not (c in quoted_string_chars) then + break; + STATE_QCHAR: + State := STATE_QTEXT; + STATE_QUOTE: + if c='@' then + State := STATE_EXPECTING_SUBDOMAIN else + if c='.' then + State := STATE_LOCAL_PERIOD else + break; + STATE_LOCAL_PERIOD: + if c in atom_chars then + State := STATE_ATOM else + if c='"' then + State := STATE_QTEXT else + break; + STATE_EXPECTING_SUBDOMAIN: + if c in letters_digits then + State := STATE_SUBDOMAIN else + break; + STATE_SUBDOMAIN: + if c='.' then begin + inc(subdomains); + State := STATE_EXPECTING_SUBDOMAIN + end else + if c='-' then + State := STATE_HYPHEN else + if not (c in letters_digits) then + break; + STATE_HYPHEN: + if c in letters_digits then + State := STATE_SUBDOMAIN else + if c<>'-' then + break; + end; + if P^=#0 then begin + P := nil; + break; + end; + until false; + result := (State = STATE_SUBDOMAIN) and (subdomains >= 2); +end; + +// code below adapted from ZMatchPattern.pas - http://www.zeoslib.sourceforge.net + +procedure TMatch.MatchMain; +var RangeStart, RangeEnd: PtrInt; + c: AnsiChar; + flags: set of(Invert, MemberMatch); +begin + while ((State = sNONE) and (P <= PMax)) do begin + c := Upper[Pattern[P]]; + if T > TMax then begin + if (c = '*') and (P + 1 > PMax) then + State := sVALID else + State := sABORT; + exit; + end else + case c of + '?': ; + '*': + MatchAfterStar; + '[': begin + inc(P); + byte(flags) := 0; + if Pattern[P] in ['!','^'] then begin + include(flags, Invert); + inc(P); + end; + if (Pattern[P] = ']') then begin + State := sPATTERN; + exit; + end; + c := Upper[Text[T]]; + while Pattern[P] <> ']' do begin + RangeStart := P; + RangeEnd := P; + inc(P); + if P > PMax then begin + State := sPATTERN; + exit; + end; + if Pattern[P] = '-' then begin + inc(P); + RangeEnd := P; + if (P > PMax) or (Pattern[RangeEnd] = ']') then begin + State := sPATTERN; + exit; + end; + inc(P); + end; + if P > PMax then begin + State := sPATTERN; + exit; + end; + if RangeStart < RangeEnd then begin + if (c >= Upper[Pattern[RangeStart]]) and (c <= Upper[Pattern[RangeEnd]]) then begin + include(flags, MemberMatch); + break; + end; + end + else + if (c >= Upper[Pattern[RangeEnd]]) and (c <= Upper[Pattern[RangeStart]]) then begin + include(flags, MemberMatch); + break; + end; + end; + if ((Invert in flags) and (MemberMatch in flags)) or + not ((Invert in flags) or (MemberMatch in flags)) then begin + State := sRANGE; + exit; + end; + if MemberMatch in flags then + while (P <= PMax) and (Pattern[P] <> ']') do + inc(P); + if P > PMax then begin + State := sPATTERN; + exit; + end; + end; + else + if c <> Upper[Text[T]] then + State := sLITERAL; + end; + inc(P); + inc(T); + end; + if State = sNONE then + if T <= TMax then + State := sEND else + State := sVALID; +end; + +procedure TMatch.MatchAfterStar; +var retryT, retryP: PtrInt; +begin + if (TMax = 1) or (P = PMax) then begin + State := sVALID; + exit; + end else + if (PMax = 0) or (TMax = 0) then begin + State := sABORT; + exit; + end; + while ((T <= TMax) and (P < PMax)) and (Pattern[P] in ['?', '*']) do begin + if Pattern[P] = '?' then + inc(T); + inc(P); + end; + if T >= TMax then begin + State := sABORT; + exit; + end else + if P >= PMax then begin + State := sVALID; + exit; + end; + repeat + if (Upper[Pattern[P]] = Upper[Text[T]]) or (Pattern[P] = '[') then begin + retryT := T; + retryP := P; + MatchMain; + if State = sVALID then + break; + State := sNONE; // retry until end of Text, (check below) or State valid + T := retryT; + P := retryP; + end; + inc(T); + if (T > TMax) or (P > PMax) then begin + State := sABORT; + exit; + end; + until State <> sNONE; +end; + +function SearchAny(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + aMatch.State := sNONE; + aMatch.P := 0; + aMatch.T := 0; + aMatch.Text := aText; + aMatch.TMax := aTextLen - 1; + aMatch.MatchMain; + result := aMatch.State = sVALID; +end; + +// faster alternative (without recursion) for only * ? (but not [...]) + +function SearchNoRange(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +{$ifdef CPUX86} +var + c: AnsiChar; + pat, txt: PtrInt; // use local registers +begin + aMatch.T := 0; // aMatch.P/T are used for retry positions after * + aMatch.Text := aText; + aMatch.TMax := aTextLen - 1; + pat := 0; + txt := 0; + repeat + if pat <= aMatch.PMax then begin + c := aMatch.Pattern[pat]; + case c of + '?': + if txt <= aMatch.TMax then begin + inc(pat); + inc(txt); + continue; + end; + '*': begin + aMatch.P := pat; + aMatch.T := txt + 1; + inc(pat); + continue; + end; + else if (txt <= aMatch.TMax) and (c = aMatch.Text[txt]) then begin + inc(pat); + inc(txt); + continue; + end; + end; + end + else if txt > aMatch.TMax then + break; + txt := aMatch.T; + if (txt > 0) and (txt <= aMatch.TMax + 1) then begin + inc(aMatch.T); + pat := aMatch.P+1; + continue; + end; + result := false; + exit; + until false; + result := true; +end; +{$else} // optimized for x86_64/ARM with more registers +var + c: AnsiChar; + pat, patend, txtend, txtretry, patretry: PUTF8Char; +label + fin; +begin + pat := pointer(aMatch.Pattern); + if pat = nil then + goto fin; + patend := pat + aMatch.PMax; + patretry := nil; + txtend := aText + aTextLen - 1; + txtretry := nil; + repeat + if pat <= patend then begin + c := pat^; + if c <> '*' then + if c <> '?' then begin + if (aText <= txtend) and (c = aText^) then begin + inc(pat); + inc(aText); + continue; + end; + end + else begin // '?' + if aText <= txtend then begin + inc(pat); + inc(aText); + continue; + end; + end + else begin // '*' + inc(pat); + txtretry := aText + 1; + patretry := pat; + continue; + end; + end + else if aText > txtend then + break; + if (PtrInt(txtretry)> 0) and (txtretry <= txtend + 1) then begin + aText := txtretry; + inc(txtretry); + pat := patretry; + continue; + end; +fin:result := false; + exit; + until false; + result := true; +end; +{$endif CPUX86} + +function SearchNoRangeU(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +var + c: AnsiChar; + pat, txt: PtrInt; +begin + aMatch.T := 0; + aMatch.Text := aText; + aMatch.TMax := aTextLen - 1; + pat := 0; + txt := 0; + repeat + if pat <= aMatch.PMax then begin + c := aMatch.Pattern[pat]; + case c of + '?': + if txt <= aMatch.TMax then begin + inc(pat); + inc(txt); + continue; + end; + '*': begin + aMatch.P := pat; + aMatch.T := txt + 1; + inc(pat); + continue; + end; + else if (txt <= aMatch.TMax) and + (aMatch.Upper[c] = aMatch.Upper[aMatch.Text[txt]]) then begin + inc(pat); + inc(txt); + continue; + end; + end; + end + else if txt > aMatch.TMax then + break; + txt := aMatch.T; + if (txt > 0) and (txt <= aMatch.TMax + 1) then begin + inc(aMatch.T); + pat := aMatch.P+1; + continue; + end; + result := false; + exit; + until false; + result := true; +end; + +function SimpleContainsU(t, tend, p: PUTF8Char; pmax: PtrInt; up: PNormTable): boolean; + {$ifdef HASINLINE}inline;{$endif} +// brute force case-insensitive search p[0..pmax] in t..tend-1 +var first: AnsiChar; + i: PtrInt; +label next; +begin + first := up[p^]; + repeat + if up[t^] <> first then begin +next: inc(t); + if t < tend then + continue else + break; + end; + for i := 1 to pmax do + if up[t[i]] <> up[p[i]] then + goto next; + result := true; + exit; + until false; + result := false; +end; + +{$ifdef CPU64} // naive but very efficient code generation on FPC x86-64 +function SimpleContains8(t, tend, p: PUTF8Char; pmax: PtrInt): boolean; inline; +label next; +var i, first: PtrInt; +begin + first := PPtrInt(p)^; + repeat + if PPtrInt(t)^ <> first then begin +next: inc(t); + if t < tend then + continue else + break; + end; + for i := 8 to pmax do + if t[i] <> p[i] then + goto next; + result := true; + exit; + until false; + result := false; +end; +{$endif CPU64} + +function SimpleContains4(t, tend, p: PUTF8Char; pmax: PtrInt): boolean; + {$ifdef HASINLINE}inline;{$endif} +label next; +var i: PtrInt; +{$ifdef CPUX86} // circumvent lack of registers for this CPU +begin + repeat + if PCardinal(t)^ <> PCardinal(p)^ then begin +{$else} + first: cardinal; +begin + first := PCardinal(p)^; + repeat + if PCardinal(t)^ <> first then begin +{$endif} +next: inc(t); + if t < tend then + continue else + break; + end; + for i := 4 to pmax do + if t[i] <> p[i] then + goto next; + result := true; + exit; + until false; + result := false; +end; + +function SimpleContains1(t, tend, p: PUTF8Char; pmax: PtrInt): boolean; + {$ifdef HASINLINE}inline;{$endif} +label next; +var i: PtrInt; +{$ifdef CPUX86} +begin + repeat + if t^ <> p^ then begin +{$else} + first: AnsiChar; +begin + first := p^; + repeat + if t^ <> first then begin +{$endif} +next: inc(t); + if t < tend then + continue else + break; + end; + for i := 1 to pmax do + if t[i] <> p[i] then + goto next; + result := true; + exit; + until false; + result := false; +end; + +function CompareMemU(P1, P2: PUTF8Char; len: PtrInt; U: PNormTable): Boolean; + {$ifdef FPC}inline;{$endif} +begin // here we know that len>0 + result := false; + repeat + dec(len); + if U[P1[len]] <> U[P2[len]] then + exit; + until len = 0; + result := true; +end; + +function SearchVoid(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + result := aTextLen = 0; +end; + +function SearchNoPattern(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + result := (aMatch.PMax + 1 = aTextlen) and CompareMem(aText, aMatch.Pattern, aTextLen); +end; + +function SearchNoPatternU(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + result := (aMatch.PMax + 1 = aTextlen) and CompareMemU(aText, aMatch.Pattern, aTextLen, aMatch.Upper); +end; + +function SearchContainsValid(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + result := true; +end; + +function SearchContainsU(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + dec(aTextLen, aMatch.PMax); + if aTextLen > 0 then + result := SimpleContainsU(aText, aText + aTextLen, aMatch.Pattern, aMatch.PMax, aMatch.Upper) + else + result := false; +end; + +function SearchContains1(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + dec(aTextLen, aMatch.PMax); + if aTextLen > 0 then + result := SimpleContains1(aText, aText + aTextLen, aMatch.Pattern, aMatch.PMax) + else + result := false; +end; + +function SearchContains4(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + dec(aTextLen, aMatch.PMax); + if aTextLen > 0 then + result := SimpleContains4(aText, aText + aTextLen, aMatch.Pattern, aMatch.PMax) + else + result := false; +end; + +{$ifdef CPU64} +function SearchContains8(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin // optimized e.g. to search an IP address as '*12.34.56.78*' in logs + dec(aTextLen, aMatch.PMax); + if aTextLen > 0 then + result := SimpleContains8(aText, aText + aTextLen, aMatch.Pattern, aMatch.PMax) + else + result := false; +end; +{$endif CPU64} + +function SearchStartWith(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + result := (aMatch.PMax < aTextlen) and CompareMem(aText, aMatch.Pattern, aMatch.PMax + 1); +end; + +function SearchStartWithU(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + result := (aMatch.PMax < aTextlen) and CompareMemU(aText, aMatch.Pattern, aMatch.PMax + 1, aMatch.Upper); +end; + +function SearchEndWith(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + dec(aTextLen, aMatch.PMax); + result := (aTextlen >= 0) and CompareMem(aText + aTextLen, aMatch.Pattern, aMatch.PMax); +end; + +function SearchEndWithU(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + dec(aTextLen, aMatch.PMax); + result := (aTextlen >= 0) and CompareMemU(aText + aTextLen, aMatch.Pattern, aMatch.PMax, aMatch.Upper); +end; + +procedure TMatch.Prepare(const aPattern: RawUTF8; aCaseInsensitive, aReuse: boolean); +begin + Prepare(pointer(aPattern), length(aPattern), aCaseInsensitive, aReuse); +end; + +procedure TMatch.Prepare(aPattern: PUTF8Char; aPatternLen: integer; + aCaseInsensitive, aReuse: boolean); +const SPECIALS: PUTF8Char = '*?['; +begin + Pattern := aPattern; + PMax := aPatternLen - 1; // search in Pattern[0..PMax] + if Pattern = nil then begin + Search := SearchVoid; + exit; + end; + if aCaseInsensitive and not IsCaseSensitive(aPattern,aPatternLen) then + aCaseInsensitive := false; // don't slow down e.g. number or IP search + if aCaseInsensitive then + Upper := @NormToUpperAnsi7 else + Upper := @NormToNorm; + Search := nil; + if aReuse then + if strcspn(Pattern, SPECIALS) > PMax then + if aCaseInsensitive then + Search := SearchNoPatternU + else + Search := SearchNoPattern + else if PMax > 0 then begin + if Pattern[PMax] = '*' then begin + if strcspn(Pattern + 1, SPECIALS) = PMax - 1 then + case Pattern[0] of + '*': begin // *something* + inc(Pattern); + dec(PMax, 2); // trim trailing and ending * + if PMax < 0 then + Search := SearchContainsValid + else if aCaseInsensitive then + Search := SearchContainsU + {$ifdef CPU64} + else if PMax >= 7 then + Search := SearchContains8 + {$endif} + else if PMax >= 3 then + Search := SearchContains4 + else + Search := SearchContains1; + end; + '?': + if aCaseInsensitive then + Search := SearchNoRangeU + else + Search := SearchNoRange; + '[': + Search := SearchAny; + else begin + dec(PMax); // trim trailing * + if aCaseInsensitive then + Search := SearchStartWithU + else + Search := SearchStartWith; + end; + end; + end + else if (Pattern[0] = '*') and (strcspn(Pattern + 1, SPECIALS) >= PMax) then begin + inc(Pattern); // jump leading * + if aCaseInsensitive then + Search := SearchEndWithU + else + Search := SearchEndWith; + end; + end else + if Pattern[0] in ['*','?'] then + Search := SearchContainsValid; + if not Assigned(Search) then begin + aPattern := PosChar(Pattern, '['); + if (aPattern = nil) or (aPattern - Pattern > PMax) then + if aCaseInsensitive then + Search := SearchNoRangeU + else + Search := SearchNoRange + else + Search := SearchAny; + end; +end; + +type // Holub and Durian (2005) SBNDM2 algorithm + // see http://www.cri.haifa.ac.il/events/2005/string/presentations/Holub.pdf + TSBNDMQ2Mask = array[AnsiChar] of cardinal; + PSBNDMQ2Mask = ^TSBNDMQ2Mask; + +function SearchSBNDMQ2ComputeMask(const Pattern: RawUTF8; u: PNormTable): RawByteString; +var + i: PtrInt; + p: PAnsiChar absolute Pattern; + m: PSBNDMQ2Mask absolute result; + c: PCardinal; +begin + SetLength(result, SizeOf(m^)); + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(m^, SizeOf(m^), 0); + for i := 0 to length(Pattern) - 1 do begin + c := @m^[u[p[i]]]; // for FPC code generation + c^ := c^ or (1 shl i); + end; +end; + +function SearchSBNDMQ2(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +var + mask: PSBNDMQ2Mask; + max, i, j: PtrInt; + state: cardinal; +begin + mask := pointer(aMatch^.Pattern); // was filled by SearchSBNDMQ2ComputeMask() + max := aMatch^.PMax; + i := max - 1; + dec(aTextLen); + if i < aTextLen then begin + repeat + state := mask[aText[i+1]] shr 1; // in two steps for better FPC codegen + state := state and mask[aText[i]]; + if state = 0 then begin + inc(i, max); // fast skip + if i >= aTextLen then + break; + continue; + end; + j := i - max; + repeat + dec(i); + if i < 0 then + break; + state := (state shr 1) and mask[aText[i]]; + until state = 0; + if i = j then begin + result := true; + exit; + end; + inc(i, max); + if i >= aTextLen then + break; + until false; + end; + result := false; +end; + +function SearchSBNDMQ2U(aMatch: PMatch; aText: PUTF8Char; aTextLen: PtrInt): boolean; +var + u: PNormTable; + mask: PSBNDMQ2Mask; + max, i, j: PtrInt; + state: cardinal; +begin + mask := pointer(aMatch^.Pattern); + max := aMatch^.PMax; + u := aMatch^.Upper; + i := max - 1; + dec(aTextLen); + if i < aTextLen then begin + repeat + state := mask[u[aText[i+1]]] shr 1; + state := state and mask[u[aText[i]]]; + if state = 0 then begin + inc(i, max); + if i >= aTextLen then + break; + continue; + end; + j := i - max; + repeat + dec(i); + if i < 0 then + break; + state := (state shr 1) and mask[u[aText[i]]]; + until state = 0; + if i = j then begin + result := true; + exit; + end; + inc(i, max); + if i >= aTextLen then + break; + until false; + end; + result := false; +end; + +procedure TMatch.PrepareContains(var aPattern: RawUTF8; + aCaseInsensitive: boolean); +begin + PMax := length(aPattern) - 1; + if aCaseInsensitive and not IsCaseSensitive(pointer(aPattern), PMax + 1) then + aCaseInsensitive := false; + if aCaseInsensitive then + Upper := @NormToUpperAnsi7 + else + Upper := @NormToNorm; + if PMax < 0 then + Search := SearchContainsValid + else if PMax > 30 then + if aCaseInsensitive then + Search := SearchContainsU + else + Search := {$ifdef CPU64}SearchContains8{$else}SearchContains4{$endif} + else if PMax >= 1 then begin // len in [2..31] = PMax in [1..30] + aPattern := SearchSBNDMQ2ComputeMask(aPattern, Upper); // lookup table + if aCaseInsensitive then + Search := SearchSBNDMQ2U + else + Search := SearchSBNDMQ2; + end + else if aCaseInsensitive then + Search := SearchContainsU + else + Search := SearchContains1; // todo: use IndexByte() on FPC? + Pattern := pointer(aPattern); +end; + +procedure TMatch.PrepareRaw(aPattern: PUTF8Char; aPatternLen: integer; + aSearch: TMatchSearchFunction); +begin + Pattern := aPattern; + PMax := aPatternLen - 1; // search in Pattern[0..PMax] + Search := aSearch; +end; + +function TMatch.Match(const aText: RawUTF8): boolean; +begin + if aText <> '' then + result := Search(@self, pointer(aText), length(aText)) + else + result := PMax < 0; +end; + +function TMatch.Match(aText: PUTF8Char; aTextLen: PtrInt): boolean; +begin + if (aText <> nil) and (aTextLen > 0) then + result := Search(@self, aText, aTextLen) + else + result := PMax < 0; +end; + +function TMatch.MatchThreadSafe(const aText: RawUTF8): boolean; +var local: TMatch; // thread-safe with no lock! +begin + local := self; + if aText <> '' then + result := local.Search(@local, pointer(aText), length(aText)) + else + result := local.PMax < 0; +end; + +function TMatch.MatchString(const aText: string): boolean; +var + local: TMatch; // thread-safe with no lock! + temp: TSynTempBuffer; + len: integer; +begin + if aText = '' then begin + result := PMax < 0; + exit; + end; + len := length(aText); + temp.Init(len * 3); + {$ifdef UNICODE} + len := RawUnicodeToUtf8(temp.buf, temp.len + 1, pointer(aText), len, [ccfNoTrailingZero]); + {$else} + len := CurrentAnsiConvert.AnsiBufferToUTF8(temp.buf, pointer(aText), len) - temp.buf; + {$endif} + local := self; + result := local.Search(@local, temp.buf, len); + temp.Done; +end; + +function TMatch.Equals(const aAnother{$ifndef DELPHI5OROLDER}: TMatch{$endif}): boolean; +begin + result := (PMax = TMatch(aAnother).PMax) and (Upper = TMatch(aAnother).Upper) and + CompareMem(Pattern, TMatch(aAnother).Pattern, PMax + 1); +end; + +function TMatch.PatternLength: integer; +begin + result := PMax + 1; +end; + +function TMatch.PatternText: PUTF8Char; +begin + result := Pattern; +end; + +function IsMatch(const Pattern, Text: RawUTF8; CaseInsensitive: boolean): boolean; +var match: TMatch; +begin + match.Prepare(Pattern, CaseInsensitive, {reuse=}false); + result := match.Match(Text); +end; + +function IsMatchString(const Pattern, Text: string; CaseInsensitive: boolean): boolean; +var match: TMatch; + pat, txt: RawUTF8; +begin + StringToUTF8(Pattern, pat); // local variable is mandatory for FPC + StringToUTF8(Text, txt); + match.Prepare(pat, CaseInsensitive, {reuse=}false); + result := match.Match(txt); +end; + +function SetMatchs(const CSVPattern: RawUTF8; CaseInsensitive: boolean; + out Match: TMatchDynArray): integer; +var P, S: PUTF8Char; +begin + result := 0; + P := pointer(CSVPattern); + if P <> nil then + repeat + S := P; + while not (P^ in [#0, ',']) do + inc(P); + if P <> S then begin + SetLength(Match, result + 1); + Match[result].Prepare(S, P - S, CaseInsensitive, {reuse=}true); + inc(result); + end; + if P^ = #0 then + break; + inc(P); + until false; +end; + +function SetMatchs(CSVPattern: PUTF8Char; CaseInsensitive: boolean; + Match: PMatch; MatchMax: integer): integer; +var S: PUTF8Char; +begin + result := 0; + if (CSVPattern <> nil) and (MatchMax >= 0) then + repeat + S := CSVPattern; + while not (CSVPattern^ in [#0, ',']) do + inc(CSVPattern); + if CSVPattern <> S then begin + Match^.Prepare(S, CSVPattern - S, CaseInsensitive, {reuse=}true); + inc(result); + if result > MatchMax then + break; + inc(Match); + end; + if CSVPattern^ = #0 then + break; + inc(CSVPattern); + until false; +end; + +function MatchExists(const One: TMatch; const Several: TMatchDynArray): boolean; +var + i: integer; +begin + result := true; + for i := 0 to high(Several) do + if Several[i].Equals(One) then + exit; + result := false; +end; + +function MatchAdd(const One: TMatch; var Several: TMatchDynArray): boolean; +var + n: integer; +begin + result := not MatchExists(One, Several); + if result then begin + n := length(Several); + SetLength(Several, n + 1); + Several[n] := One; + end; +end; + +function MatchAny(const Match: TMatchDynArray; const Text: RawUTF8): boolean; +var + m: PMatch; + i: integer; +begin + result := true; + if Match = nil then + exit; + m := pointer(Match); + for i := 1 to length(Match) do + if m^.Match(Text) then + exit + else + inc(m); + result := false; +end; + +procedure FilterMatchs(const CSVPattern: RawUTF8; CaseInsensitive: boolean; + var Values: TRawUTF8DynArray); +var + match: TMatchDynArray; + m, n, i: integer; +begin + if SetMatchs(CSVPattern, CaseInsensitive, match) = 0 then + exit; + n := 0; + for i := 0 to high(Values) do + for m := 0 to high(match) do + if match[m].Match(Values[i]) then begin + if i <> n then + Values[n] := Values[i]; + inc(n); + break; + end; + if n <> length(Values) then + SetLength(Values, n); +end; + + +{ TMatchs } + +constructor TMatchs.Create(const aPatterns: TRawUTF8DynArray; CaseInsensitive: Boolean); +begin + inherited Create; + Subscribe(aPatterns, CaseInsensitive); +end; + +function TMatchs.Match(const aText: RawUTF8): integer; +begin + result := Match(pointer(aText), length(aText)); +end; + +function TMatchs.Match(aText: PUTF8Char; aLen: integer): integer; +var + one: ^TMatchStore; + local: TMatch; // thread-safe with no lock! +begin + if (self = nil) or (fMatch = nil) then + result := -1 // no filter by name -> allow e.g. to process everything + else begin + one := pointer(fMatch); + if aLen <> 0 then begin + for result := 0 to fMatchCount - 1 do begin + local := one^.Pattern; + if local.Search(@local, aText, aLen) then + exit; + inc(one); + end; + end + else + for result := 0 to fMatchCount - 1 do + if one^.Pattern.PMax < 0 then + exit + else + inc(one); + result := -2; + end; +end; + +function TMatchs.MatchString(const aText: string): integer; +var + temp: TSynTempBuffer; + len: integer; +begin + len := length(aText); + temp.Init(len * 3); + {$ifdef UNICODE} + len := RawUnicodeToUtf8(temp.buf, temp.len + 1, pointer(aText), len, [ccfNoTrailingZero]); + {$else} + len := CurrentAnsiConvert.AnsiBufferToUTF8(temp.buf, pointer(aText), len, true) - temp.buf; + {$endif} + result := Match(temp.buf, len); + temp.Done; +end; + +procedure TMatchs.Subscribe(const aPatternsCSV: RawUTF8; CaseInsensitive: Boolean); +var + patterns: TRawUTF8DynArray; +begin + CSVToRawUTF8DynArray(pointer(aPatternsCSV), patterns); + Subscribe(patterns, CaseInsensitive); +end; + +procedure TMatchs.Subscribe(const aPatterns: TRawUTF8DynArray; CaseInsensitive: Boolean); +var + i, j, m, n: integer; + found: ^TMatchStore; + pat: PRawUTF8; +begin + m := length(aPatterns); + if m = 0 then + exit; + n := fMatchCount; + SetLength(fMatch, n + m); + pat := pointer(aPatterns); + for i := 1 to m do begin + found := pointer(fMatch); + for j := 1 to n do + if StrComp(pointer(found^.PatternInstance), pointer(pat^)) = 0 then begin + found := nil; + break; + end + else + inc(found); + if found <> nil then + with fMatch[n] do begin + PatternInstance := pat^; // avoid GPF if aPatterns[] is released + Pattern.Prepare(PatternInstance, CaseInsensitive, {reuse=}true); + inc(n); + end; + inc(pat); + end; + fMatchCount := n; + if n <> length(fMatch) then + SetLength(fMatch, n); +end; + + +{ TSynFilterOrValidate } + +constructor TSynFilterOrValidate.Create(const aParameters: RawUTF8); +begin + inherited Create; + SetParameters(aParameters); // should parse the JSON-encoded parameters +end; + +constructor TSynFilterOrValidate.CreateUTF8(const Format: RawUTF8; + const Args, Params: array of const); +begin + Create(FormatUTF8(Format,Args,Params,true)); +end; + +procedure TSynFilterOrValidate.SetParameters(const value: RawUTF8); +begin + fParameters := value; +end; + +function TSynFilterOrValidate.AddOnce(var aObjArray: TSynFilterOrValidateObjArray; + aFreeIfAlreadyThere: boolean): TSynFilterOrValidate; +var i: integer; +begin + if self<>nil then begin + for i := 0 to length(aObjArray)-1 do + if (PPointer(aObjArray[i])^=PPointer(self)^) and + (aObjArray[i].fParameters=fParameters) then begin + if aFreeIfAlreadyThere then + Free; + result := aObjArray[i]; + exit; + end; + ObjArrayAdd(aObjArray,self); + end; + result := self; +end; + + +{ TSynFilterUpperCase } + +procedure TSynFilterUpperCase.Process(aFieldIndex: integer; var value: RawUTF8); +begin + value := SynCommons.UpperCase(value); +end; + + +{ TSynFilterUpperCaseU } + +procedure TSynFilterUpperCaseU.Process(aFieldIndex: integer; var value: RawUTF8); +begin + value := UpperCaseU(value); +end; + + +{ TSynFilterLowerCase } + +procedure TSynFilterLowerCase.Process(aFieldIndex: integer; var value: RawUTF8); +begin + value := LowerCase(value); +end; + + +{ TSynFilterLowerCaseU } + +procedure TSynFilterLowerCaseU.Process(aFieldIndex: integer; var value: RawUTF8); +begin + value := LowerCaseU(value); +end; + + +{ TSynFilterTrim } + +procedure TSynFilterTrim.Process(aFieldIndex: integer; var value: RawUTF8); +begin + value := Trim(value); +end; + + +{ TSynFilterTruncate} + +procedure TSynFilterTruncate.SetParameters(const value: RawUTF8); +var V: array[0..1] of TValuePUTF8Char; + tmp: TSynTempBuffer; +begin + tmp.Init(value); + JSONDecode(tmp.buf,['MaxLength','UTF8Length'],@V); + fMaxLength := GetCardinalDef(V[0].Value,0); + fUTF8Length := V[1].Idem('1') or V[1].Idem('true'); + tmp.Done; +end; + +procedure TSynFilterTruncate.Process(aFieldIndex: integer; var value: RawUTF8); +begin + if fMaxLength-163 then + break; // exceeded 63-character limit of a DNS name + if (ForbiddenDomains<>'') and (FindCSVIndex(pointer(ForbiddenDomains),DOM)>=0) then + break; + i := length(value); + while (i>0) and (value[i]<>'.') do dec(i); + TLD := lowercase(copy(value,i+1,100)); + if (AllowedTLD<>'') and (FindCSVIndex(pointer(AllowedTLD),TLD)<0) then + break; + if (ForbiddenTLD<>'') and (FindCSVIndex(pointer(ForbiddenTLD),TLD)>=0) then + break; + if not fAnyTLD then + if FastFindPUTF8CharSorted(@TopLevelTLD,high(TopLevelTLD),pointer(TLD))<0 then + if length(TLD)<>2 then + break; // assume a two chars string is a ISO 3166-1 alpha-2 code + result := true; + exit; + until true; + ErrorMsg := Format(sInvalidEmailAddress,[UTF8ToString(value)]); + result := false; +end; + +procedure TSynValidateEmail.SetParameters(const value: RawUTF8); +var V: array[0..3] of TValuePUTF8Char; + tmp: TSynTempBuffer; +begin + inherited; + tmp.Init(value); + JSONDecode(tmp.buf,['AllowedTLD','ForbiddenTLD','ForbiddenDomains','AnyTLD'],@V); + LowerCaseCopy(V[0].Value,V[0].ValueLen,fAllowedTLD); + LowerCaseCopy(V[1].Value,V[1].ValueLen,fForbiddenTLD); + LowerCaseCopy(V[2].Value,V[2].ValueLen,fForbiddenDomains); + AnyTLD := V[3].Idem('1') or V[3].Idem('true'); + tmp.Done; +end; + + +{ TSynValidatePattern } + +procedure TSynValidatePattern.SetParameters(const Value: RawUTF8); +begin + inherited SetParameters(Value); + fMatch.Prepare(Value, ClassType=TSynValidatePatternI, {reuse=}true); +end; + +function TSynValidatePattern.Process(aFieldIndex: integer; const value: RawUTF8; + var ErrorMsg: string): boolean; + procedure SetErrorMsg; + begin + ErrorMsg := Format(sInvalidPattern,[UTF8ToString(value)]); + end; +begin + result := fMatch.Match(value); + if not result then + SetErrorMsg; +end; + + +{ TSynValidateNonVoidText } + +function Character01n(n: integer): string; +begin + if n<0 then + n := 0 else + if n>1 then + n := 2; + result := GetCSVItemString(pointer(string(sCharacter01n)),n); +end; + +procedure InvalidTextLengthMin(min: integer; var result: string); +begin + result := Format(sInvalidTextLengthMin,[min,Character01n(min)]); +end; + +function TSynValidateNonVoidText.Process(aFieldIndex: integer; const value: RawUTF8; + var ErrorMsg: string): boolean; +begin + if value='' then begin + InvalidTextLengthMin(1,ErrorMsg); + result := false; + end else + result := true; +end; + + +{ TSynValidateText } + +procedure TSynValidateText.SetErrorMsg(fPropsIndex, InvalidTextIndex, + MainIndex: integer; var result: string); +var P: PChar; +begin + P := pointer(string(sInvalidTextChar)); + result := GetCSVItemString(P,MainIndex); + if fPropsIndex>0 then + result := Format(result, + [fProps[fPropsIndex],GetCSVItemString(P,InvalidTextIndex), + Character01n(fProps[fPropsIndex])]); +end; + +function TSynValidateText.Process(aFieldIndex: integer; const value: RawUTF8; + var ErrorMsg: string): boolean; +var i, L: cardinal; + Min: array[2..7] of cardinal; +begin + result := false; + if fUTF8Length then + L := length(value) else + L := Utf8ToUnicodeLength(pointer(value)); + if LMaxLength then + ErrorMsg := Format(sInvalidTextLengthMax,[MaxLength,Character01n(MaxLength)]) else begin + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Min,SizeOf(Min),0); + L := length(value); + for i := 1 to L do + case value[i] of + ' ': + inc(Min[7]); + 'a'..'z': begin + inc(Min[2]); + inc(Min[5]); + end; + 'A'..'Z': begin + inc(Min[2]); + inc(Min[6]); + end; + '0'..'9': + inc(Min[3]); + '_','!',';','.',',','/',':','?','%','$','=','"','#','@','(',')','{','}', + '+','''','-','*': + inc(Min[4]); + end; + for i := 2 to 7 do + if Min[i]fProps[i+8] then begin + SetErrorMsg(i+8,i,1,ErrorMsg); + exit; + end; + if value<>'' then begin + if MaxLeftTrimCountMaxLeftTrimCount then begin + SetErrorMsg(0,0,8,ErrorMsg); + exit; + end; + end; + if MaxRightTrimCountMaxRightTrimCount then begin + SetErrorMsg(0,0,9,ErrorMsg); + exit; + end; + end; + end; + result := true; + end; +end; + +procedure TSynValidateText.SetParameters(const value: RawUTF8); +var V: array[0..high(TSynValidateTextProps)+1] of TValuePUTF8Char; + i: integer; + tmp: TSynTempBuffer; +const DEFAULT: TSynValidateTextProps = ( + 1,maxInt,0,0,0,0,0,0,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt); +begin + if (MinLength=0) and (MaxLength=0) then // if not previously set + fProps := DEFAULT; + inherited SetParameters(value); + if value='' then + exit; + tmp.Init(value); + try + JSONDecode(tmp.buf,['MinLength','MaxLength', + 'MinAlphaCount','MinDigitCount','MinPunctCount', + 'MinLowerCount','MinUpperCount','MinSpaceCount', + 'MaxLeftTrimCount','MaxRightTrimCount', + 'MaxAlphaCount','MaxDigitCount','MaxPunctCount', + 'MaxLowerCount','MaxUpperCount','MaxSpaceCount', + 'UTF8Length'],@V); + for i := 0 to high(fProps) do + fProps[i] := GetCardinalDef(V[i].Value,fProps[i]); + with V[high(V)] do + fUTF8Length := Idem('1') or Idem('true'); + finally + tmp.Done; + end; +end; + + +{ TSynValidatePassWord } + +procedure TSynValidatePassWord.SetParameters(const value: RawUTF8); +const DEFAULT: TSynValidateTextProps = ( + 5,20,1,1,1,1,1,0,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,0); +begin + // set default values for validating a strong password + fProps := DEFAULT; + // read custom parameters + inherited; +end; + + +{ ************ low-level buffer processing functions************************* } + +{ TSynBloomFilter } + +const + BLOOM_VERSION = 0; + BLOOM_MAXHASH = 32; // only 7 is needed for 1% false positive ratio + +constructor TSynBloomFilter.Create(aSize: integer; aFalsePositivePercent: double); +const LN2 = 0.69314718056; +begin + inherited Create; + if aSize < 0 then + fSize := 1000 else + fSize := aSize; + if aFalsePositivePercent<=0 then + fFalsePositivePercent := 1 else + if aFalsePositivePercent>100 then + fFalsePositivePercent := 100 else + fFalsePositivePercent := aFalsePositivePercent; + // see http://stackoverflow.com/a/22467497 + fBits := Round(-ln(fFalsePositivePercent/100)*aSize/(LN2*LN2)); + fHashFunctions := Round(fBits/fSize*LN2); + if fHashFunctions=0 then + fHashFunctions := 1 else + if fHashFunctions>BLOOM_MAXHASH then + fHashFunctions := BLOOM_MAXHASH; + Reset; +end; + +constructor TSynBloomFilter.Create(const aSaved: RawByteString; aMagic: cardinal); +begin + inherited Create; + if not LoadFrom(aSaved,aMagic) then + raise ESynException.CreateUTF8('%.Create with invalid aSaved content',[self]); +end; + +procedure TSynBloomFilter.Insert(const aValue: RawByteString); +begin + Insert(pointer(aValue),length(aValue)); +end; + +procedure TSynBloomFilter.Insert(aValue: pointer; aValueLen: integer); +var h: integer; + h1,h2: cardinal; // https://goo.gl/Pls5wi +begin + if (self=nil) or (aValueLen<=0) or (fBits=0) then + exit; + h1 := crc32c(0,aValue,aValueLen); + if fHashFunctions=1 then + h2 := 0 else + h2 := crc32c(h1,aValue,aValueLen); + Safe.Lock; + try + for h := 0 to fHashFunctions-1 do begin + SetBitPtr(pointer(fStore),h1 mod fBits); + inc(h1,h2); + end; + inc(fInserted); + finally + Safe.UnLock; + end; +end; + +function TSynBloomFilter.GetInserted: cardinal; +begin + Safe.Lock; + try + result := fInserted; // Delphi 5 does not support LockedInt64[] + finally + Safe.UnLock; + end; +end; + +function TSynBloomFilter.MayExist(const aValue: RawByteString): boolean; +begin + result := MayExist(pointer(aValue),length(aValue)); +end; + +function TSynBloomFilter.MayExist(aValue: pointer; aValueLen: integer): boolean; +var h: integer; + h1,h2: cardinal; // https://goo.gl/Pls5wi +begin + result := false; + if (self=nil) or (aValueLen<=0) or (fBits=0) then + exit; + h1 := crc32c(0,aValue,aValueLen); + if fHashFunctions=1 then + h2 := 0 else + h2 := crc32c(h1,aValue,aValueLen); + Safe.Lock; + try + for h := 0 to fHashFunctions-1 do + if GetBitPtr(pointer(fStore),h1 mod fBits) then + inc(h1,h2) else + exit; + finally + Safe.UnLock; + end; + result := true; +end; + +procedure TSynBloomFilter.Reset; +begin + Safe.Lock; + try + if fStore='' then + SetLength(fStore,(fBits shr 3)+1); + FillcharFast(pointer(fStore)^,length(fStore),0); + fInserted := 0; + finally + Safe.UnLock; + end; +end; + +function TSynBloomFilter.SaveTo(aMagic: cardinal): RawByteString; +var W: TFileBufferWriter; + BufLen: integer; + temp: array[word] of byte; +begin + BufLen := length(fStore)+100; + if BufLen<=SizeOf(temp) then + W := TFileBufferWriter.Create(TRawByteStringStream,@temp,SizeOf(temp)) else + W := TFileBufferWriter.Create(TRawByteStringStream,BufLen); + try + SaveTo(W,aMagic); + W.Flush; + result := TRawByteStringStream(W.Stream).DataString; + finally + W.Free; + end; +end; + +procedure TSynBloomFilter.SaveTo(aDest: TFileBufferWriter; aMagic: cardinal=$B1003F11); +begin + aDest.Write4(aMagic); + aDest.Write1(BLOOM_VERSION); + Safe.Lock; + try + aDest.Write8(fFalsePositivePercent); + aDest.Write4(fSize); + aDest.Write4(fBits); + aDest.Write1(fHashFunctions); + aDest.Write4(fInserted); + ZeroCompress(pointer(fStore),Length(fStore),aDest); + finally + Safe.UnLock; + end; +end; + +function TSynBloomFilter.LoadFrom(const aSaved: RawByteString; aMagic: cardinal): boolean; +begin + result := LoadFrom(pointer(aSaved),length(aSaved)); +end; + +function TSynBloomFilter.LoadFrom(P: PByte; PLen: integer; aMagic: cardinal): boolean; +var start: PByte; + version: integer; +begin + result := false; + start := P; + if (P=nil) or (PLen<32) or (PCardinal(P)^<>aMagic) then + exit; + inc(P,4); + version := P^; inc(P); + if version>BLOOM_VERSION then + exit; + Safe.Lock; + try + fFalsePositivePercent := PDouble(P)^; inc(P,8); + if (fFalsePositivePercent<=0) or (fFalsePositivePercent>100) then + exit; + fSize := PCardinal(P)^; inc(P,4); + fBits := PCardinal(P)^; inc(P,4); + if fBits=BLOOM_MAXHASH then + exit; + Reset; + fInserted := PCardinal(P)^; inc(P,4); + ZeroDecompress(P,PLen-(PAnsiChar(P)-PAnsiChar(start)),fStore); + result := length(fStore)=integer(fBits shr 3)+1; + finally + Safe.UnLock; + end; +end; + + +{ TSynBloomFilterDiff } + +type + TBloomDiffHeader = packed record + kind: (bdDiff,bdFull,bdUpToDate); + size: cardinal; + inserted: cardinal; + revision: Int64; + crc: cardinal; + end; + +procedure TSynBloomFilterDiff.Insert(aValue: pointer; aValueLen: integer); +begin + Safe.Lock; + try + inherited Insert(aValue,aValueLen); + inc(fRevision); + inc(fSnapshotInsertCount); + finally + Safe.UnLock; + end; +end; + +procedure TSynBloomFilterDiff.Reset; +begin + Safe.Lock; + try + inherited Reset; + fSnapshotAfterInsertCount := fSize shr 5; + fSnapShotAfterMinutes := 30; + fSnapshotTimestamp := 0; + fSnapshotInsertCount := 0; + fRevision := UnixTimeUTC shl 31; + fKnownRevision := 0; + fKnownStore := ''; + finally + Safe.UnLock; + end; +end; + +procedure TSynBloomFilterDiff.DiffSnapshot; +begin + Safe.Lock; + try + fKnownRevision := fRevision; + fSnapshotInsertCount := 0; + SetString(fKnownStore,PAnsiChar(pointer(fStore)),length(fStore)); + if fSnapShotAfterMinutes=0 then + fSnapshotTimestamp := 0 else + fSnapshotTimestamp := GetTickCount64+fSnapShotAfterMinutes*60000; + finally + Safe.UnLock; + end; +end; + +function TSynBloomFilterDiff.SaveToDiff(const aKnownRevision: Int64): RawByteString; +var head: TBloomDiffHeader; + W: TFileBufferWriter; + temp: array[word] of byte; +begin + Safe.Lock; + try + if aKnownRevision=fRevision then + head.kind := bdUpToDate else + if (fKnownRevision=0) or + (fSnapshotInsertCount>fSnapshotAfterInsertCount) or + ((fSnapshotInsertCount>0) and (fSnapshotTimestamp<>0) and + (GetTickCount64>fSnapshotTimestamp)) then begin + DiffSnapshot; + head.kind := bdFull; + end else + if (aKnownRevisionfRevision) then + head.kind := bdFull else + head.kind := bdDiff; + head.size := length(fStore); + head.inserted := fInserted; + head.revision := fRevision; + head.crc := crc32c(0,@head,SizeOf(head)-SizeOf(head.crc)); + if head.kind=bdUpToDate then begin + SetString(result,PAnsiChar(@head),SizeOf(head)); + exit; + end; + if head.size+100<=SizeOf(temp) then + W := TFileBufferWriter.Create(TRawByteStringStream,@temp,SizeOf(temp)) else + W := TFileBufferWriter.Create(TRawByteStringStream,head.size+100); + try + W.Write(@head,SizeOf(head)); + case head.kind of + bdFull: + SaveTo(W); + bdDiff: + ZeroCompressXor(pointer(fStore),pointer(fKnownStore),head.size,W); + end; + W.Flush; + result := TRawByteStringStream(W.Stream).DataString; + finally + W.Free; + end; + finally + Safe.UnLock; + end; +end; + +function TSynBloomFilterDiff.DiffKnownRevision(const aDiff: RawByteString): Int64; +var head: ^TBloomDiffHeader absolute aDiff; +begin + if (length(aDiff)high(head.kind)) or + (head.size<>cardinal(length(fStore))) or + (head.crc<>crc32c(0,pointer(head),SizeOf(head^)-SizeOf(head.crc))) then + result := 0 else + result := head.Revision; +end; + +function TSynBloomFilterDiff.LoadFromDiff(const aDiff: RawByteString): boolean; +var head: ^TBloomDiffHeader absolute aDiff; + P: PByte; + PLen: integer; +begin + result := false; + P := pointer(aDiff); + PLen := length(aDiff); + if (PLenhigh(head.kind)) or + (head.crc<>crc32c(0,pointer(head),SizeOf(head^)-SizeOf(head.crc))) then + exit; + if (fStore<>'') and (head.size<>cardinal(length(fStore))) then + exit; + inc(P,SizeOf(head^)); + dec(PLen,SizeOf(head^)); + Safe.Lock; + try + case head.kind of + bdFull: + result := LoadFrom(P,PLen); + bdDiff: + if fStore<>'' then + result := ZeroDecompressOr(pointer(P),Pointer(fStore),PLen,head.size); + bdUpToDate: + result := true; + end; + if result then begin + fRevision := head.revision; + fInserted := head.inserted; + end; + finally + Safe.UnLock; + end; +end; + +procedure ZeroCompress(P: PAnsiChar; Len: integer; Dest: TFileBufferWriter); +var PEnd,beg,zero: PAnsiChar; + crc: cardinal; +begin + Dest.WriteVarUInt32(Len); + PEnd := P+Len; + beg := P; + crc := 0; + while P#0) and (P3 then begin + Len := zero-beg; + crc := crc32c(crc,beg,Len); + Dest.WriteVarUInt32(Len); + Dest.Write(beg,Len); + Len := P-zero; + crc := crc32c(crc,@Len,SizeOf(Len)); + Dest.WriteVarUInt32(Len-3); + beg := P; + end; + end; + Len := P-beg; + if Len>0 then begin + crc := crc32c(crc,beg,Len); + Dest.WriteVarUInt32(Len); + Dest.Write(beg,Len); + end; + Dest.Write4(crc); +end; + +procedure ZeroCompressXor(New,Old: PAnsiChar; Len: cardinal; Dest: TFileBufferWriter); +var beg,same,index,crc,L: cardinal; +begin + Dest.WriteVarUInt32(Len); + beg := 0; + index := 0; + crc := 0; + while indexOld[index]) and (index3 then begin + Dest.WriteVarUInt32(same-beg); + Dest.WriteXor(New+beg,Old+beg,same-beg,@crc); + crc := crc32c(crc,@L,SizeOf(L)); + Dest.WriteVarUInt32(L-3); + beg := index; + end; + end; + L := index-beg; + if L>0 then begin + Dest.WriteVarUInt32(L); + Dest.WriteXor(New+beg,Old+beg,L,@crc); + end; + Dest.Write4(crc); +end; + +procedure ZeroDecompress(P: PByte; Len: integer; {$ifdef FPC}var{$else}out{$endif} Dest: RawByteString); +var PEnd,D,DEnd: PAnsiChar; + DestLen,crc: cardinal; +begin + PEnd := PAnsiChar(P)+Len-4; + DestLen := FromVarUInt32(P); + SetString(Dest,nil,DestLen); // FPC uses var + D := pointer(Dest); + DEnd := D+DestLen; + crc := 0; + while PAnsiChar(P)DEnd then + break; + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,D^,Len); + crc := crc32c(crc,D,Len); + inc(P,Len); + inc(D,Len); + if PAnsiChar(P)>=PEnd then + break; + Len := FromVarUInt32(P)+3; + if D+Len>DEnd then + break; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(D^,Len,0); + crc := crc32c(crc,@Len,SizeOf(Len)); + inc(D,Len); + end; + if crc<>PCardinal(P)^ then + Dest := ''; +end; + +function ZeroDecompressOr(P,Dest: PAnsiChar; Len,DestLen: integer): boolean; +var PEnd,DEnd: PAnsiChar; + crc: cardinal; +begin + PEnd := P+Len-4; + if cardinal(DestLen)<>FromVarUInt32(PByte(P)) then begin + result := false; + exit; + end; + DEnd := Dest+DestLen; + crc := 0; + while (PDEnd then + break; + crc := crc32c(crc,P,Len); + OrMemory(pointer(Dest),pointer(P),Len); + inc(P,Len); + inc(Dest,Len); + if P>=PEnd then + break; + Len := FromVarUInt32(PByte(P))+3; + crc := crc32c(crc,@Len,SizeOf(Len)); + inc(Dest,Len); + end; + result := crc=PCardinal(P)^; +end; + + +function Max(a,b: PtrInt): PtrInt; {$ifdef HASINLINE}inline;{$endif} +begin + if a > b then + result := a else + result := b; +end; + +function Min(a,b: PtrInt): PtrInt; {$ifdef HASINLINE}inline;{$endif} +begin + if a < b then + result := a else + result := b; +end; + +function Comp(a,b: PAnsiChar; len: PtrInt): PtrInt; +{$ifdef HASINLINE} inline; +var lenptr: PtrInt; +begin + result := 0; + lenptr := len-SizeOf(PtrInt); + if lenptr>=0 then + repeat + if PPtrInt(a+result)^<>PPtrInt(b+result)^ then + break; + inc(result,SizeOf(PtrInt)); + until result>lenptr; + if resultb[result] then + exit; + inc(result); + until result=len; +end; +{$else} // eax = a, edx = b, ecx = len +asm // the 'rep cmpsb' version is slower on Intel Core CPU (not AMD) + or ecx,ecx + push ebx + push ecx + jz @ok +@1: mov bx,[eax] + lea eax,[eax+2] + cmp bl,[edx] + jne @ok + dec ecx + jz @ok + cmp bh,[edx+1] + lea edx,[edx+2] + jne @ok + dec ecx + jnz @1 +@ok: pop eax + sub eax,ecx + pop ebx +end; +{$endif} + +function CompReverse(a,b: pointer; len: PtrInt): PtrInt; +begin + result := 0; + if len>0 then + repeat + if PByteArray(a)[-result]<>PByteArray(b)[-result] then + exit; + inc(result); + until result=len; +end; + +procedure movechars(s,d: PAnsiChar; t: PtrUInt); + {$ifdef HASINLINE}inline;{$endif} +// this code is sometimes used rather than MoveFast() for overlapping copy +begin + dec(PtrUInt(s), PtrUInt(d)); + inc(t, PtrUInt(d)); + repeat + d^ := s[PtrUInt(d)]; + inc(d); + until PtrUInt(d)=t; +end; + +function WriteCurOfs(curofs,curlen,curofssize: integer; sp: PAnsiChar): PAnsiChar; +begin + if curlen=0 then begin + sp^ := #0; + inc(sp); + end else begin + sp := Pointer(ToVarUInt32(curlen,PByte(sp))); + PInteger(sp)^ := curofs; + inc(sp,curofssize); + end; + result := sp; +end; + +{$ifdef CPUINTEL} // crc32c SSE4.2 hardware accellerated dword hash +function crc32csse42(buf: pointer): cardinal; +{$ifdef CPUX86} +asm + mov edx, eax + xor eax, eax + {$ifdef ISDELPHI2010} + crc32 eax, dword ptr[edx] + {$else} + db $F2, $0F, $38, $F1, $02 + {$endif} +end; +{$else} {$ifdef FPC}nostackframe; assembler; asm {$else} +asm // ecx=buf (Linux: edi=buf) + .noframe +{$endif FPC} + xor eax, eax + crc32 eax, dword ptr[buf] +end; +{$endif CPUX86} +{$endif CPUINTEL} + +function hash32prime(buf: pointer): cardinal; +begin // xxhash32-inspired - and won't pollute L1 cache with lookup tables + result := PCardinal(buf)^; + result := result xor (result shr 15); + result := result * 2246822519; + result := result xor (result shr 13); + result := result * 3266489917; + result := result xor (result shr 16); +end; + +const + HTabBits = 18; // fits well with DeltaCompress(..,BufSize=2MB) + HTabMask = (1 shl HTabBits)-1; // =$3ffff + HListMask = $ffffff; // HTab[]=($ff,$ff,$ff) + +type + PHTab = ^THTab; // HTabBits=18 -> SizeOf=767KB + THTab = packed array[0..HTabMask] of array[0..2] of byte; + +function DeltaCompute(NewBuf, OldBuf, OutBuf, WorkBuf: PAnsiChar; + NewBufSize, OldBufSize, MaxLevel: PtrInt; HList, HTab: PHTab): PAnsiChar; +var i, curofs, curlen, curlevel, match, curofssize, h, oldh: PtrInt; + sp, pInBuf, pOut: PAnsiChar; + ofs: cardinal; + spb: PByte absolute sp; + hash: function(buf: pointer): cardinal; +begin + // 1. fill HTab[] with hashes for all old data + {$ifdef CPUINTEL} + if cfSSE42 in CpuFeatures then + hash := @crc32csse42 else + {$endif} + hash := @hash32prime; + FillCharFast(HTab^,SizeOf(HTab^),$ff); // HTab[]=HListMask by default + pInBuf := OldBuf; + oldh := -1; // force calculate first hash + sp := pointer(HList); + for i := 0 to OldBufSize-3 do begin + h := hash(pInBuf) and HTabMask; + inc(pInBuf); + if h=oldh then + continue; + oldh := h; + h := PtrInt(@HTab^[h]); // fast 24-bit data process + PCardinal(sp)^ := PCardinal(h)^; + PCardinal(h)^ := cardinal(i) or (PCardinal(h)^ and $ff000000); + inc(sp,3); + end; + // 2. compression init + if OldBufSize<=$ffff then + curofssize := 2 else + curofssize := 3; + curlen := -1; + curofs := 0; + pOut := OutBuf+7; + sp := WorkBuf; + // 3. handle identical leading bytes + match := Comp(OldBuf,NewBuf,Min(OldBufSize,NewBufSize)); + if match>2 then begin + sp := WriteCurOfs(0,match,curofssize,sp); + sp^ := #0; inc(sp); + inc(NewBuf,match); + dec(NewBufSize,match); + end; + pInBuf := NewBuf; + // 4. main loop: identify longest sequences using hash, and store reference + if NewBufSize>=8 then + repeat + // hash 4 next bytes from NewBuf, and find longest match in OldBuf + ofs := PCardinal(@HTab^[hash(NewBuf) and HTabMask])^ and HListMask; + if ofs<>HListMask then begin // brute force search loop of best hash match + curlevel := MaxLevel; + repeat + with PHash128Rec(OldBuf+ofs)^ do + {$ifdef CPU64} // test 8 bytes + if PHash128Rec(NewBuf)^.Lo=Lo then begin + {$else} + if (PHash128Rec(NewBuf)^.c0=c0) and (PHash128Rec(NewBuf)^.c1=c1) then begin + {$endif} + match := Comp(@PHash128Rec(NewBuf)^.c2,@c2,Min(PtrUInt(OldBufSize)-ofs,NewBufSize)-8); + if match>curlen then begin // found a longer sequence + curlen := match; + curofs := ofs; + end; + end; + dec(curlevel); + ofs := PCardinal(@HList^[ofs])^ and HListMask; + until (ofs=HListMask) or (curlevel=0); + end; + // curlen = longest sequence length + if curlen<0 then begin // no sequence found -> copy one byte + dec(NewBufSize); + pOut^ := NewBuf^; + inc(NewBuf); + inc(pOut); + if NewBufSize>8 then // >=8 may overflow + continue else + break; + end; + inc(curlen,8); + sp := WriteCurOfs(curofs,curlen,curofssize,sp); + spb := ToVarUInt32(NewBuf-pInBuf,spb); + inc(NewBuf,curlen); // continue to search after the sequence + dec(NewBufSize,curlen); + curlen := -1; + pInBuf := NewBuf; + if NewBufSize>8 then // >=8 may overflow + continue else + break; + until false; + // 5. write remaining bytes + if NewBufSize>0 then begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(NewBuf^,pOut^,NewBufSize); + inc(pOut,NewBufSize); + inc(newBuf,NewBufSize); + end; + sp^ := #0; inc(sp); + spb := ToVarUInt32(NewBuf-pInBuf,spb); + // 6. write header + PInteger(OutBuf)^ := pOut-OutBuf-7; + h := sp-WorkBuf; + PInteger(OutBuf+3)^ := h; + OutBuf[6] := AnsiChar(curofssize); + // 7. copy commands + {$ifdef FPC}Move{$else}MoveFast{$endif}(WorkBuf^,pOut^,h); + result := pOut+h; +end; + +function ExtractBuf(GoodCRC: cardinal; p: PAnsiChar; var aUpd, Delta: PAnsiChar; + Old: PAnsiChar): TDeltaError; +var pEnd, buf, upd, src: PAnsiChar; + bufsize, datasize, leading, srclen: PtrUInt; + curofssize: byte; +begin + // 1. decompression init + upd := aUpd; + bufsize := PCardinal(p)^ and $00ffffff; inc(p,3); + datasize := PCardinal(p)^ and $00ffffff; inc(p,3); + curofssize := ord(p^); inc(p); + buf := p; inc(p,bufsize); + pEnd := p+datasize; + src := nil; + // 2. main loop + while p0 then + if curofssize=2 then begin + src := Old+PWord(p)^; + inc(p,2); + end else begin + src := Old+PCardinal(p)^ and $00ffffff; + inc(p,3); + end; + // copy leading bytes + leading := FromVarUInt32(PByte(P)); + if leading<>0 then begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(buf^,upd^,leading); + inc(buf,leading); + inc(upd,leading); + end; + // copy sequence + if srclen<>0 then begin + if PtrUInt(upd-src) direct copy of whole block + CreateCopied; + exit; + end; + // 2. compression init + bigfile := OldSize>BufSize; + if BufSize>NewSize then + BufSize := NewSize; + if BufSize>$ffffff then + BufSize := $ffffff; // we store offsets with 2..3 bytes -> max 16MB chunk + Trailing := 0; + Getmem(workbuf,BufSize); // compression temporary buffers + Getmem(HList,BufSize*SizeOf(HList[0])); + Getmem(HTab,SizeOf(HTab^)); + Getmem(Delta,Max(NewSize,OldSize)+4096); // Delta size max evalulation + try + d := Delta; + db := ToVarUInt32(NewSize,db); // Destination Size + // 3. handle leading and trailing identical bytes (for biggest files) + if bigfile then begin + BufRead := Comp(New,Old,Min(NewSize,OldSize)); // test 1st same chars + if BufRead>9 then begin // it happens very often + db := ToVarUInt32(BufRead,db); // blockSize = Size BufIdem + WriteByte(d,FLAG_BEGIN); + WriteInt(d,crc32c(0,New,BufRead)); + inc(New,BufRead); + dec(NewSize,BufRead); + inc(Old,BufRead); + dec(OldSize,BufRead); + end; // test last same chars + BufRead := CompReverse(New+NewSize-1,Old+OldSize-1,Min(NewSize,OldSize)); + if BufRead>5 then begin + if NewSize=BufRead then + dec(BufRead); // avoid block overflow + dec(OldSize,BufRead); + dec(NewSize,BufRead); + Trailing := BufRead; + end; + end; + // 4. main loop + repeat + BufRead := Min(BufSize,NewSize); + dec(NewSize,BufRead); + if (BufRead=0) and (Trailing>0) then begin + db := ToVarUInt32(Trailing,db); + WriteByte(d,FLAG_END); // block idem end flag -> BufRead := 0 not necessary + WriteInt(d,crc32c(0,New,Trailing)); + break; + end; + OldRead := Min(BufSize,OldSize); + dec(OldSize,OldRead); + db := ToVarUInt32(OldRead,db); + If (BufRead<4) or (OldRead<4) or (BufRead div 4>OldRead) then begin + WriteByte(d,FLAG_COPIED); // block copied flag + db := ToVarUInt32(BufRead,db); + if BufRead=0 then + break; + WriteInt(d,crc32c(0,New,BufRead)); + {$ifdef FPC}Move{$else}MoveFast{$endif}(New^,d^,BufRead); + inc(New,BufRead); + inc(d,BufRead); + end else begin + WriteByte(d,FLAG_COMPRESS); // block compressed flag + WriteInt(d,crc32c(0,New,BufRead)); + WriteInt(d,crc32c(0,Old,OldRead)); + d := DeltaCompute(New,Old,d,workbuf,BufRead,OldRead,Level,HList,HTab); + inc(New,BufRead); + inc(Old,OldRead); + end; + until false; + // 5. release temp memory + finally + result := d-Delta; + Freemem(HTab); + Freemem(HList); + Freemem(workbuf); + end; + if result>=NewSizeSave+17 then begin + // Delta didn't compress well -> store it (with 17 bytes overhead) + Freemem(Delta); + CreateCopied; + end; +end; + +function DeltaCompress(const New, Old: RawByteString; + Level, BufSize: integer): RawByteString; +begin + result := DeltaCompress(pointer(New),pointer(Old), + length(New),length(Old),Level,BufSize); +end; + +function DeltaCompress(New, Old: PAnsiChar; NewSize, OldSize: integer; + Level, BufSize: integer): RawByteString; +var Delta: PAnsiChar; + DeltaLen: integer; +begin + DeltaLen := DeltaCompress(New,Old,Newsize,OldSize,Delta,Level,BufSize); + SetString(result,Delta,DeltaLen); + Freemem(Delta); +end; + +function DeltaExtract(Delta,Old,New: PAnsiChar): TDeltaError; +var BufCRC: Cardinal; + Code: Byte; + Len, BufRead, OldRead: PtrInt; + db: PByte absolute Delta; + Upd: PAnsiChar; +begin + Result := dsSuccess; + Len := FromVarUInt32(db); + Upd := New; + repeat + OldRead := FromVarUInt32(db); + Code := db^; inc(db); + Case Code of + FLAG_COPIED: begin // block copied flag - copy new from Delta + BufRead := FromVarUInt32(db); + If BufRead=0 then + break; + If crc32c(0,Delta+4,BufRead)<>PCardinal(Delta)^ then begin + result := dsCrcCopy; + exit; + end; + inc(Delta,4); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Delta^,New^,BufRead); + if BufRead>=Len then + exit; // if Old=nil -> only copy new + inc(Delta,BufRead); + inc(New,BufRead); + end; + FLAG_COMPRESS: begin // block compressed flag - extract Delta from Old + BufCRC := PCardinal(Delta)^; inc(Delta,4); + if crc32c(0,Old,OldRead)<>PCardinal(Delta)^ then begin + result := dsCrcComp; + exit; + end; + inc(Delta,4); + result := ExtractBuf(BufCRC,Delta,New,Delta,Old); + if result<>dsSuccess then + exit; + end; + FLAG_BEGIN: begin // block idem begin flag + if crc32c(0,Old,OldRead)<>PCardinal(Delta)^ then begin + result := dsCrcBegin; + exit; + end; + inc(Delta,4); + {$ifdef FPC}Move{$else}MoveFast{$endif}(Old^,New^,OldRead); + inc(New,OldRead); + end; + FLAG_END: begin // block idem end flag + if crc32c(0,Old,OldRead)<>PCardinal(Delta)^ then + Result := dsCrcEnd; + {$ifdef FPC}Move{$else}MoveFast{$endif}(Old^,New^,OldRead); + inc(New,OldRead); + break; + end; + else begin + result := dsFlag; + exit; + end; + end; // Case Code of + inc(Old,OldRead); + until false; + if New-Upd<>Len then + result := dsLen; +end; + +function DeltaExtract(const Delta,Old: RawByteString; out New: RawByteString): TDeltaError; +begin + if (Delta='') or (Delta='=') then begin + New := Old; + result := dsSuccess; + end else begin + SetLength(New,DeltaExtractSize(pointer(Delta))); + result := DeltaExtract(pointer(Delta),pointer(Old),pointer(New)); + end; +end; + +function DeltaExtractSize(const Delta: RawByteString): integer; +begin + result := DeltaExtractSize(pointer(Delta)); +end; + +function DeltaExtractSize(Delta: PAnsiChar): integer; +begin + if Delta=nil then + result := 0 else + result := FromVarUInt32(PByte(Delta)); +end; + +function ToText(err: TDeltaError): PShortString; +begin + result := GetEnumName(TypeInfo(TDeltaError),ord(err)); +end; + + +{ TFastReader } + +procedure TFastReader.Init(Buffer: pointer; Len: integer); +begin + P := Buffer; + Last := P+Len; + OnErrorOverflow := nil; + OnErrorData := nil; + Tag := 0; +end; + +procedure TFastReader.Init(const Buffer: RawByteString); +begin + Init(pointer(Buffer),length(Buffer)); +end; + +procedure TFastReader.ErrorOverflow; +begin + if Assigned(OnErrorOverflow) then + OnErrorOverflow else + raise EFastReader.Create('Reached End of Input'); +end; + +procedure TFastReader.ErrorData(const fmt: RawUTF8; const args: array of const); +begin + if Assigned(OnErrorData) then + OnErrorData(fmt,args) else + raise EFastReader.CreateUTF8('Incorrect Data: '+fmt,args); +end; + +function TFastReader.EOF: boolean; +begin + result := P>=Last; +end; + +function TFastReader.RemainingLength: PtrUInt; +begin + result := PtrUInt(Last)-PtrUInt(P); +end; + +function TFastReader.NextByte: byte; +begin + if P>=Last then + ErrorOverflow; + result := ord(P^); + inc(P); +end; + +function TFastReader.NextByteSafe(dest: pointer): boolean; +begin + if P>=Last then + result := false + else begin + PAnsiChar(dest)^ := P^; + inc(P); + result := true; + end; +end; + +function TFastReader.Next4: cardinal; +begin + if P+3>=Last then + ErrorOverflow; + result := PCardinal(P)^; + inc(P,4); +end; + +function TFastReader.Next8: QWord; +begin + if P+7>=Last then + ErrorOverflow; + result := PQWord(P)^; + inc(P,8); +end; + +function TFastReader.NextByteEquals(Value: byte): boolean; +begin + if P>=Last then + ErrorOverflow; + if ord(P^) = Value then begin + inc(P); + result := true; + end else + result := false; +end; + +function TFastReader.Next(DataLen: PtrInt): pointer; +begin + if P+DataLen>Last then + ErrorOverflow; + result := P; + inc(P,DataLen); +end; + +function TFastReader.NextSafe(out Data: Pointer; DataLen: PtrInt): boolean; +begin + if P+DataLen>Last then + result := false else begin + Data := P; + inc(P,DataLen); + result := true; + end; +end; + +procedure TFastReader.Copy(out Dest; DataLen: PtrInt); +begin + if P+DataLen>Last then + ErrorOverflow; + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,Dest,DataLen); + inc(P,DataLen); +end; + +function TFastReader.CopySafe(out Dest; DataLen: PtrInt): boolean; +begin + if P+DataLen>Last then + result := false else begin + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,Dest,DataLen); + inc(P,DataLen); + result := true; + end; +end; + +procedure TFastReader.VarBlob(out result: TValueResult); +begin + result.Len := VarUInt32; + if P+result.Len>Last then + ErrorOverflow; + result.Ptr := P; + inc(P,result.Len); +end; + +function TFastReader.VarBlob: TValueResult; +begin + result.Len := VarUInt32; + if P+result.Len>Last then + ErrorOverflow; + result.Ptr := P; + inc(P,result.Len); +end; + +{$ifndef NOVARIANTS} +procedure TFastReader.NextVariant(var Value: variant; CustomVariantOptions: pointer); +begin + P := VariantLoad(Value,P,CustomVariantOptions); + if P=nil then + ErrorData('VariantLoad=nil',[]) else + if P>Last then + ErrorOverFlow; +end; + +procedure TFastReader.NextDocVariantData(out Value: variant; CustomVariantOptions: pointer); +var json: TValueResult; + temp: TSynTempBuffer; +begin + VarBlob(json); + if json.Len<=0 then + exit; + temp.Init(json.Ptr,json.Len); // parsing will modify input buffer in-place + try + if CustomVariantOptions=nil then + CustomVariantOptions := @JSON_OPTIONS[true]; + TDocVariantData(Value).InitJSONInPlace(temp.buf,PDocVariantOptions(CustomVariantOptions)^); + finally + temp.Done; + end; +end; +{$endif NOVARIANTS} + +function TFastReader.VarInt32: integer; +begin + result := VarUInt32; + if result and 1<>0 then + // 1->1, 3->2.. + result := result shr 1+1 else + // 0->0, 2->-1, 4->-2.. + result := -(result shr 1); +end; + +function TFastReader.VarInt64: Int64; +begin + result := VarUInt64; + if result and 1<>0 then + // 1->1, 3->2.. + result := result shr 1+1 else + // 0->0, 2->-1, 4->-2.. + result := -(result shr 1); +end; + +function TFastReader.VarUInt32: cardinal; +var c: cardinal; +{$ifdef CPUX86} // not enough CPU registers +label err; +begin + result := ord(P^); + if P>=Last then + goto err; + inc(P); + if result<=$7f then + exit; + if P>=Last then + goto err; + c := ord(P^) shl 7; + inc(P); + result := result and $7F or c; + if c<=$7f shl 7 then + exit; // Values between 128 and 16256 + if P>=Last then + goto err; + c := ord(P^) shl 14; + inc(P); + result := result and $3FFF or c; + if c<=$7f shl 14 then + exit; // Values between 16257 and 2080768 + if P>=Last then + goto err; + c := ord(P^) shl 21; + inc(P); + result := result and $1FFFFF or c; + if c<=$7f shl 21 then + exit; // Values between 2080769 and 266338304 + if P>=Last then +err:ErrorOverflow; + c := ord(P^) shl 28; + inc(P); + result := result and $FFFFFFF or c; +{$else} + s,l: PByte; +label err,fin; +begin + s := pointer(P); + l := pointer(Last); + result := s^; + if PAnsiChar(s)>=PAnsiChar(l) then + goto err; + inc(s); + if result<=$7f then + goto fin; + if PAnsiChar(s)>=PAnsiChar(l) then + goto err; + c := s^ shl 7; + inc(s); + result := result and $7F or c; + if c<=$7f shl 7 then + goto fin; // Values between 128 and 16256 + if PAnsiChar(s)>=PAnsiChar(l) then + goto err; + c := s^ shl 14; + inc(s); + result := result and $3FFF or c; + if c<=$7f shl 14 then + goto fin; // Values between 16257 and 2080768 + if PAnsiChar(s)>=PAnsiChar(l) then + goto err; + c := s^ shl 21; + inc(s); + result := result and $1FFFFF or c; + if c<=$7f shl 21 then + goto fin; // Values between 2080769 and 266338304 + if PAnsiChar(s)>=PAnsiChar(l) then +err:ErrorOverflow; + c := s^ shl 28; + inc(s); + result := result and $FFFFFFF or c; +fin: + P := pointer(s); +{$endif} +end; + +procedure TFastReader.VarNextInt; +{$ifdef CPUX86} // not enough CPU registers +begin + repeat + if P>=Last then + break; // reached end of input + if P^<=#$7f then + break; // reached end of VarUInt32/VarUInt64 + inc(P); + until false; + inc(P); +{$else} +var s: PAnsiChar; +begin + s := P; + repeat + if s>=Last then + break; // reached end of input + if s^<=#$7f then + break; // reached end of VarUInt32/VarUInt64 + inc(s); + until false; + P := s+1; +{$endif CPUX86} +end; + +procedure TFastReader.VarNextInt(count: integer); +{$ifdef CPUX86} // not enough CPU registers +begin + if count=0 then + exit; + repeat + if P>=Last then + break; // reached end of input + if P^>#$7f then begin + inc(P); + continue; // didn't reach end of VarUInt32/VarUInt64 + end; + inc(P); + dec(count); + if count=0 then + break; + until false; +{$else} +var s, max: PAnsiChar; +begin + if count=0 then + exit; + s := P; + max := Last; + repeat + if s>=max then + break; // reached end of input + if s^>#$7f then begin + inc(s); + continue; // didn't reach end of VarUInt32/VarUInt64 + end; + inc(s); + dec(count); + if count=0 then + break; + until false; + P := s; +{$endif CPUX86} +end; + +function TFastReader.PeekVarInt32(out value: PtrInt): boolean; +begin + result := PeekVarUInt32(PtrUInt(value)); + if result then + if value and 1<>0 then + // 1->1, 3->2.. + value := value shr 1+1 else + // 0->0, 2->-1, 4->-2.. + value := -(value shr 1); +end; + +function TFastReader.PeekVarUInt32(out value: PtrUInt): boolean; +var s: PAnsiChar; +begin + result := false; + s := P; + repeat + if s>=Last then + exit; // reached end of input -> returns false + if s^<=#$7f then + break; // reached end of VarUInt32 + inc(s); + until false; + s := P; + value := VarUInt32; // fast value decode + P := s; // rewind + result := true; +end; + +function TFastReader.VarUInt32Safe(out Value: cardinal): boolean; +var c, n, v: cardinal; +begin + result := false; + if P>=Last then + exit; + v := ord(P^); + inc(P); + if v>$7f then begin + n := 0; + v := v and $7F; + repeat + if P>=Last then + exit; + c := ord(P^); + inc(P); + inc(n,7); + if c<=$7f then break; + v := v or ((c and $7f) shl n); + until false; + v := v or (c shl n); + end; + Value := v; + result := true; // success +end; + +function TFastReader.VarUInt64: QWord; +label err; +var c, n: PtrUInt; +begin + if P>=Last then +err: ErrorOverflow; + c := ord(P^); + inc(P); + if c>$7f then begin + result := c and $7F; + n := 0; + repeat + if P>=Last then + goto err; + c := ord(P^); + inc(P); + inc(n,7); + if c<=$7f then break; + result := result or (QWord(c and $7f) shl n); + until false; + result := result or (QWord(c) shl n); + end else + result := c; +end; + +function TFastReader.VarString: RawByteString; +begin + with VarBlob do + SetString(result,Ptr,Len); +end; + +procedure TFastReader.VarUTF8(out result: RawUTF8); +begin + with VarBlob do + FastSetString(result,Ptr,Len); +end; + +function TFastReader.VarUTF8: RawUTF8; +begin + with VarBlob do + FastSetString(result,Ptr,Len); +end; + +function TFastReader.VarShortString: shortstring; +begin + with VarBlob do + SetString(result,Ptr,Len); +end; + +function TFastReader.VarUTF8Safe(out Value: RawUTF8): boolean; +var len: cardinal; +begin + if VarUInt32Safe(len) then + if len=0 then + result := true else + if P+len<=Last then begin + FastSetString(Value,P,len); + inc(P,len); + result := true; + end else + result := false else + result := false; +end; + +procedure TFastReader.Read(var DA: TDynArray; NoCheckHash: boolean); +begin + P := DA.LoadFrom(P,nil,NoCheckHash); + if P=nil then + ErrorData('TDynArray.LoadFrom %',[DA.ArrayTypeShort^]); +end; + +function TFastReader.ReadVarUInt32Array(var Values: TIntegerDynArray): PtrInt; +var i: integer; + k: TFileBufferWriterKind; +begin + result := VarUInt32; + SetLength(Values,result); + Copy(k,1); + if k=wkUInt32 then begin + Copy(Values[0],result*4); + exit; + end; + Next(4); // format: Isize+varUInt32s + case k of + wkVarInt32: for i := 0 to result-1 do Values[i] := VarInt32; + wkVarUInt32: for i := 0 to result-1 do Values[i] := VarUInt32; + else ErrorData('ReadVarUInt32Array: unhandled kind=%', [ord(k)]); + end; +end; + +function TFastReader.ReadCompressed(Load: TAlgoCompressLoad; BufferOffset: integer): RawByteString; +var comp: PAnsiChar; + complen: PtrUInt; +begin + complen := VarUInt32; + comp := Next(complen); + TAlgoCompress.Algo(comp,complen).Decompress(comp,complen,result,Load,BufferOffset); +end; + + +{ TSynPersistentStore } + +constructor TSynPersistentStore.Create(const aName: RawUTF8); +begin + Create; + fName := aName; +end; + +constructor TSynPersistentStore.CreateFrom(const aBuffer: RawByteString; + aLoad: TAlgoCompressLoad); +begin + CreateFromBuffer(pointer(aBuffer),length(aBuffer),aLoad); +end; + +constructor TSynPersistentStore.CreateFromBuffer( + aBuffer: pointer; aBufferLen: integer; aLoad: TAlgoCompressLoad); +begin + Create(''); + LoadFrom(aBuffer,aBufferLen,aLoad); +end; + +constructor TSynPersistentStore.CreateFromFile(const aFileName: TFileName; + aLoad: TAlgoCompressLoad); +begin + Create(''); + LoadFromFile(aFileName,aLoad); +end; + +procedure TSynPersistentStore.LoadFromReader; +begin + fReader.VarUTF8(fName); +end; + +procedure TSynPersistentStore.SaveToWriter(aWriter: TFileBufferWriter); +begin + aWriter.Write(fName); +end; + +procedure TSynPersistentStore.LoadFrom(const aBuffer: RawByteString; + aLoad: TAlgoCompressLoad); +begin + if aBuffer <> '' then + LoadFrom(pointer(aBuffer),length(aBuffer),aLoad); +end; + +procedure TSynPersistentStore.LoadFrom(aBuffer: pointer; aBufferLen: integer; + aLoad: TAlgoCompressLoad); +var localtemp: RawByteString; + p: pointer; + temp: PRawByteString; +begin + if (aBuffer=nil) or (aBufferLen<=0) then + exit; // nothing to load + fLoadFromLastAlgo := TAlgoCompress.Algo(aBuffer,aBufferLen); + if fLoadFromLastAlgo = nil then + fReader.ErrorData('%.LoadFrom unknown TAlgoCompress AlgoID=%', + [self,PByteArray(aBuffer)[4]]); + temp := fReaderTemp; + if temp=nil then + temp := @localtemp; + p := fLoadFromLastAlgo.Decompress(aBuffer,aBufferLen,fLoadFromLastUncompressed,temp^,aLoad); + if p=nil then + fReader.ErrorData('%.LoadFrom %.Decompress failed',[self,fLoadFromLastAlgo]); + fReader.Init(p,fLoadFromLastUncompressed); + LoadFromReader; +end; + +function TSynPersistentStore.LoadFromFile(const aFileName: TFileName; + aLoad: TAlgoCompressLoad): boolean; +var temp: RawByteString; +begin + temp := StringFromFile(aFileName); + result := temp<>''; + if result then + LoadFrom(temp,aLoad); +end; + +procedure TSynPersistentStore.SaveTo(out aBuffer: RawByteString; nocompression: boolean; + BufLen: integer; ForcedAlgo: TAlgoCompress; BufferOffset: integer); +var writer: TFileBufferWriter; + temp: array[word] of byte; +begin + if BufLen<=SizeOf(temp) then + writer := TFileBufferWriter.Create(TRawByteStringStream,@temp,SizeOf(temp)) else + writer := TFileBufferWriter.Create(TRawByteStringStream,BufLen); + try + SaveToWriter(writer); + fSaveToLastUncompressed := writer.TotalWritten; + aBuffer := writer.FlushAndCompress(nocompression,ForcedAlgo,BufferOffset); + finally + writer.Free; + end; +end; + +function TSynPersistentStore.SaveTo(nocompression: boolean; BufLen: integer; + ForcedAlgo: TAlgoCompress; BufferOffset: integer): RawByteString; +begin + SaveTo(result,nocompression,BufLen,ForcedAlgo,BufferOffset); +end; + +function TSynPersistentStore.SaveToFile(const aFileName: TFileName; + nocompression: boolean; BufLen: integer; ForcedAlgo: TAlgoCompress): PtrUInt; +var temp: RawByteString; +begin + SaveTo(temp,nocompression,BufLen,ForcedAlgo); + if FileFromString(temp,aFileName) then + result := length(temp) else + result := 0; +end; + + +{ TSynPersistentStoreJson } + +procedure TSynPersistentStoreJson.AddJSON(W: TTextWriter); +begin + W.AddPropJSONString('name', fName); +end; + +function TSynPersistentStoreJson.SaveToJSON(reformat: TTextWriterJSONFormat): RawUTF8; +var + W: TTextWriter; +begin + W := TTextWriter.CreateOwnedStream(65536); + try + W.Add('{'); + AddJSON(W); + W.CancelLastComma; + W.Add('}'); + W.SetText(result, reformat); + finally + W.Free; + end; +end; + + +{ TRawByteStringGroup } + +procedure TRawByteStringGroup.Add(const aItem: RawByteString); +begin + if Values=nil then + Clear; // ensure all fields are initialized, even if on stack + if Count=Length(Values) then + SetLength(Values,NextGrow(Count)); + with Values[Count] do begin + Position := self.Position; + Value := aItem; + end; + LastFind := Count; + inc(Count); + inc(Position,Length(aItem)); +end; + +procedure TRawByteStringGroup.Add(aItem: pointer; aItemLen: integer); +var tmp: RawByteString; +begin + SetString(tmp,PAnsiChar(aItem),aItemLen); + Add(tmp); +end; + +{$ifndef DELPHI5OROLDER} // circumvent Delphi 5 compiler bug + +procedure TRawByteStringGroup.Add(const aAnother: TRawByteStringGroup); +var i: integer; + s,d: PRawByteStringGroupValue; +begin + if aAnother.Values=nil then + exit; + if Values=nil then + Clear; // ensure all fields are initialized, even if on stack + if Count+aAnother.Count>Length(Values) then + SetLength(Values,Count+aAnother.Count); + s := pointer(aAnother.Values); + d := @Values[Count]; + for i := 1 to aAnother.Count do begin + d^.Position := Position; + d^.Value := s^.Value; + inc(Position,length(s^.Value)); + inc(s); + inc(d); + end; + inc(Count,aAnother.Count); + LastFind := Count-1; +end; + +procedure TRawByteStringGroup.RemoveLastAdd; +begin + if Count>0 then begin + dec(Count); + dec(Position,Length(Values[Count].Value)); + Values[Count].Value := ''; // release memory + LastFind := Count-1; + end; +end; + +function TRawByteStringGroup.Equals(const aAnother: TRawByteStringGroup): boolean; +begin + if ((Values=nil) and (aAnother.Values<>nil)) or ((Values<>nil) and (aAnother.Values=nil)) or + (Position<>aAnother.Position) then + result := false else + if (Count<>1) or (aAnother.Count<>1) or (Values[0].Value<>aAnother.Values[0].Value) then + result := AsText=aAnother.AsText else + result := true; +end; + +{$endif DELPHI5OROLDER} + +procedure TRawByteStringGroup.Clear; +begin + Values := nil; + Position := 0; + Count := 0; + LastFind := 0; +end; + +procedure TRawByteStringGroup.AppendTextAndClear(var aDest: RawByteString); +var d,i: integer; + v: PRawByteStringGroupValue; +begin + d := length(aDest); + SetLength(aDest,d+Position); + v := pointer(Values); + for i := 1 to Count do begin + {$ifdef FPC}Move{$else}MoveFast{$endif}( + pointer(v^.Value)^,PByteArray(aDest)[d+v^.Position],length(v^.Value)); + inc(v); + end; + Clear; +end; + +function TRawByteStringGroup.AsText: RawByteString; +begin + if Values=nil then + result := '' else begin + if Count>1 then + Compact; + result := Values[0].Value; + end; +end; + +procedure TRawByteStringGroup.Compact; +var i: integer; + v: PRawByteStringGroupValue; + tmp: RawByteString; +begin + if (Values<>nil) and (Count>1) then begin + SetString(tmp,nil,Position); + v := pointer(Values); + for i := 1 to Count do begin + {$ifdef FPC}Move{$else}MoveFast{$endif}( + pointer(v^.Value)^,PByteArray(tmp)[v^.Position],length(v^.Value)); + {$ifdef FPC}Finalize(v^.Value){$else}v^.Value := ''{$endif}; // free chunks + inc(v); + end; + Values[0].Value := tmp; // use result for absolute compaction ;) + if Count>128 then + SetLength(Values,128); + Count := 1; + LastFind := 0; + end; +end; + +function TRawByteStringGroup.AsBytes: TByteDynArray; +var i: integer; +begin + result := nil; + if Values=nil then + exit; + SetLength(result,Position); + for i := 0 to Count-1 do + with Values[i] do + {$ifdef FPC}Move{$else}MoveFast{$endif}( + pointer(Value)^,PByteArray(result)[Position],length(Value)); +end; + +procedure TRawByteStringGroup.Write(W: TTextWriter; Escape: TTextWriterKind); +var i: integer; +begin + if Values<>nil then + for i := 0 to Count-1 do + with Values[i] do + W.Add(PUTF8Char(pointer(Value)),length(Value),Escape); +end; + +procedure TRawByteStringGroup.WriteBinary(W: TFileBufferWriter); +var i: integer; +begin + if Values<>nil then + for i := 0 to Count-1 do + W.WriteBinary(Values[i].Value); +end; + +procedure TRawByteStringGroup.WriteString(W: TFileBufferWriter); +begin + if Values=nil then begin + W.Write1(0); + exit; + end; + W.WriteVarUInt32(Position); + WriteBinary(W); +end; + +procedure TRawByteStringGroup.AddFromReader(var aReader: TFastReader); +var complexsize: integer; +begin + complexsize := aReader.VarUInt32; + if complexsize>0 then // directly create a RawByteString from aReader buffer + Add(aReader.Next(complexsize),complexsize); +end; + +function TRawByteStringGroup.Find(aPosition: integer): PRawByteStringGroupValue; +var i: integer; +begin + if (pointer(Values)<>nil) and (cardinal(aPosition)=result^.Position) and (aPositionaPosition then begin + dec(result); + LastFind := i; + exit; + end else + inc(result); + dec(result); + LastFind := Count-1; + end + else + result := nil; +end; + +function TRawByteStringGroup.Find(aPosition, aLength: integer): pointer; +var P: PRawByteStringGroupValue; + i: integer; +label found; +begin + if (pointer(Values)<>nil) and (cardinal(aPosition)=0) and (i+aLengthaPosition then begin + LastFind := i; +found: dec(P); + dec(aPosition,P^.Position); + if aLength-aPosition<=length(P^.Value) then + result := @PByteArray(P^.Value)[aPosition] else + result := nil; + exit; + end else + inc(P); + LastFind := Count-1; + goto found; + end + else + result := nil; +end; + +procedure TRawByteStringGroup.FindAsText(aPosition, aLength: integer; out aText: RawByteString); +var P: PRawByteStringGroupValue; +begin + P := Find(aPosition); + if P=nil then + exit; + dec(aPosition,P^.Position); + if (aPosition=0) and (length(P^.Value)=aLength) then + aText := P^.Value else // direct return if not yet compacted + if aLength-aPosition<=length(P^.Value) then + SetString(aText,PAnsiChar(@PByteArray(P^.Value)[aPosition]),aLength); +end; + +function TRawByteStringGroup.FindAsText(aPosition, aLength: integer): RawByteString; +begin + FindAsText(aPosition,aLength,result); +end; + +{$ifndef NOVARIANTS} +procedure TRawByteStringGroup.FindAsVariant(aPosition, aLength: integer; out aDest: variant); +var tmp: RawByteString; +begin + tmp := FindAsText(aPosition,aLength); + if tmp <> '' then + RawUTF8ToVariant(tmp,aDest); +end; +{$endif NOVARIANTS} + +procedure TRawByteStringGroup.FindWrite(aPosition, aLength: integer; + W: TTextWriter; Escape: TTextWriterKind; TrailingCharsToIgnore: integer); +var P: pointer; +begin + P := Find(aPosition,aLength); + if P<>nil then + W.Add(PUTF8Char(P)+TrailingCharsToIgnore,aLength-TrailingCharsToIgnore,Escape); +end; + +procedure TRawByteStringGroup.FindWriteBase64(aPosition, aLength: integer; + W: TTextWriter; withMagic: boolean); +var P: pointer; +begin + P := Find(aPosition,aLength); + if P<>nil then + W.WrBase64(P,aLength,withMagic); +end; + +procedure TRawByteStringGroup.FindMove(aPosition, aLength: integer; aDest: pointer); +var P: pointer; +begin + P := Find(aPosition,aLength); + if P<>nil then + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,aDest^,aLength); +end; + + +{ ************ Security and Identifiers classes ************************** } + +{ TSynUniqueIdentifierBits } + +function TSynUniqueIdentifierBits.Counter: word; +begin + result := PWord(@Value)^ and $7fff; +end; + +function TSynUniqueIdentifierBits.ProcessID: TSynUniqueIdentifierProcess; +begin + result := (PCardinal(@Value)^ shr 15) and $ffff; +end; + +function TSynUniqueIdentifierBits.CreateTimeUnix: TUnixTime; +begin + result := Value shr 31; +end; + +{$ifndef NOVARIANTS} +function TSynUniqueIdentifierBits.AsVariant: variant; +begin + ToVariant(result); +end; + +procedure TSynUniqueIdentifierBits.ToVariant(out result: variant); +begin + TDocVariantData(result).InitObject(['Created',DateTimeToIso8601Text(CreateDateTime), + 'Identifier',ProcessID,'Counter',Counter,'Value',Value, + 'Hex',Int64ToHex(Value)],JSON_OPTIONS_FAST); +end; +{$endif NOVARIANTS} + +{$ifndef DELPHI5OROLDER} +function TSynUniqueIdentifierBits.Equal(const Another: TSynUniqueIdentifierBits): boolean; +begin + result := Value=Another.Value; +end; +{$endif} + +procedure TSynUniqueIdentifierBits.From(const AID: TSynUniqueIdentifier); +begin + Value := AID; +end; + +function TSynUniqueIdentifierBits.CreateTimeLog: TTimeLog; +begin + PTimeLogBits(@result)^.From(UnixTimeToDateTime(Value shr 31)); +end; + +function TSynUniqueIdentifierBits.CreateDateTime: TDateTime; +begin + result := UnixTimeToDateTime(Value shr 31); +end; + +function TSynUniqueIdentifierBits.ToHexa: RawUTF8; +begin + Int64ToHex(Value,result); +end; + +function TSynUniqueIdentifierBits.FromHexa(const hexa: RawUTF8): boolean; +begin + result := (Length(hexa)=16) and HexDisplayToBin(pointer(hexa),@Value,SizeOf(Value)); +end; + +procedure TSynUniqueIdentifierBits.FromDateTime(const aDateTime: TDateTime); +begin + Value := DateTimeToUnixTime(aDateTime) shl 31; +end; + +procedure TSynUniqueIdentifierBits.FromUnixTime(const aUnixTime: TUnixTime); +begin + Value := aUnixTime shl 31; +end; + + +{ TSynUniqueIdentifierGenerator } + +const // fSafe.Padding[] slots + SYNUNIQUEGEN_COMPUTECOUNT = 0; + +procedure TSynUniqueIdentifierGenerator.ComputeNew( + out result: TSynUniqueIdentifierBits); +var currentTime: cardinal; +begin + currentTime := UnixTimeUTC; // fast API (under Windows, faster than GetTickCount64) + fSafe.Lock; + try + if currentTime>fUnixCreateTime then begin + fUnixCreateTime := currentTime; + fLastCounter := 0; // reset + end; + if fLastCounter=$7fff then begin // collision (unlikely) -> cheat on timestamp + inc(fUnixCreateTime); + fLastCounter := 0; + end else + inc(fLastCounter); + result.Value := Int64(fLastCounter or fIdentifierShifted) or + (Int64(fUnixCreateTime) shl 31); + inc(fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT].VInt64); + finally + fSafe.UnLock; + end; +end; + +function TSynUniqueIdentifierGenerator.ComputeNew: Int64; +begin + ComputeNew(PSynUniqueIdentifierBits(@result)^); +end; + +function TSynUniqueIdentifierGenerator.GetComputedCount: Int64; +begin + {$ifdef NOVARIANTS} + fSafe.Lock; + result := fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT].VInt64; + fSafe.Unlock; + {$else} + result := fSafe.LockedInt64[SYNUNIQUEGEN_COMPUTECOUNT]; + {$endif} +end; + +procedure TSynUniqueIdentifierGenerator.ComputeFromDateTime(const aDateTime: TDateTime; + out result: TSynUniqueIdentifierBits); +begin // assume fLastCounter=0 + ComputeFromUnixTime(DateTimeToUnixTime(aDateTime),result); +end; + +procedure TSynUniqueIdentifierGenerator.ComputeFromUnixTime(const aUnixTime: TUnixTime; + out result: TSynUniqueIdentifierBits); +begin // assume fLastCounter=0 + result.Value := aUnixTime shl 31; + if self<>nil then + result.Value := result.Value or fIdentifierShifted; +end; + +constructor TSynUniqueIdentifierGenerator.Create(aIdentifier: TSynUniqueIdentifierProcess; + const aSharedObfuscationKey: RawUTF8); +var i, len: integer; + crc: cardinal; +begin + fIdentifier := aIdentifier; + fIdentifierShifted := aIdentifier shl 15; + fSafe.Init; + {$ifdef NOVARIANTS} + variant(fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT]) := 0; + {$else} + fSafe.LockedInt64[SYNUNIQUEGEN_COMPUTECOUNT] := 0; + {$endif} + // compute obfuscation key using hash diffusion of the supplied text + len := length(aSharedObfuscationKey); + crc := crc32ctab[0,len and 1023]; + for i := 0 to high(fCrypto)+1 do begin + crc := crc32ctab[0,crc and 1023] xor crc32ctab[3,i] xor + kr32(crc,pointer(aSharedObfuscationKey),len) xor + crc32c(crc,pointer(aSharedObfuscationKey),len) xor + fnv32(crc,pointer(aSharedObfuscationKey),len); + // do not modify those hashes above or you will break obfuscation pattern! + if i<=high(fCrypto) then + fCrypto[i] := crc else + fCryptoCRC := crc; + end; + // due to the weakness of the hash algorithms used, this approach is a bit + // naive and would be broken easily with brute force - but point here is to + // hide/obfuscate public values at end-user level (e.g. when publishing URIs), + // not implement strong security, so it sounds good enough for our purpose +end; + +destructor TSynUniqueIdentifierGenerator.Destroy; +begin + fSafe.Done; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(fCrypto,SizeOf(fCrypto),0); + fCryptoCRC := 0; + inherited Destroy; +end; + +type // compute a 24 hexadecimal chars (96 bits) obfuscated pseudo file name + TSynUniqueIdentifierObfuscatedBits = packed record + crc: cardinal; + id: TSynUniqueIdentifierBits; + end; + +function TSynUniqueIdentifierGenerator.ToObfuscated( + const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated; +var bits: TSynUniqueIdentifierObfuscatedBits; + key: cardinal; +begin + result := ''; + if aIdentifier=0 then + exit; + bits.id.Value := aIdentifier; + if self=nil then + key := 0 else + key := crc32ctab[0,bits.id.ProcessID and 1023] xor fCryptoCRC; + bits.crc := crc32c(bits.id.ProcessID,@bits.id,SizeOf(bits.id)) xor key; + if self<>nil then + bits.id.Value := bits.id.Value xor PInt64(@fCrypto[high(fCrypto)-1])^; + result := BinToHex(@bits,SizeOf(bits)); +end; + +function TSynUniqueIdentifierGenerator.FromObfuscated( + const aObfuscated: TSynUniqueIdentifierObfuscated; + out aIdentifier: TSynUniqueIdentifier): boolean; +var bits: TSynUniqueIdentifierObfuscatedBits; + len: integer; + key: cardinal; +begin + result := false; + len := PosExChar('.',aObfuscated); + if len=0 then + len := Length(aObfuscated) else + dec(len); // trim right '.jpg' + if (len<>SizeOf(bits)*2) or + not SynCommons.HexToBin(pointer(aObfuscated),@bits,SizeOf(bits)) then + exit; + if self=nil then + key := 0 else begin + bits.id.Value := bits.id.Value xor PInt64(@fCrypto[high(fCrypto)-1])^; + key := crc32ctab[0,bits.id.ProcessID and 1023] xor fCryptoCRC; + end; + if crc32c(bits.id.ProcessID,@bits.id,SizeOf(bits.id)) xor key=bits.crc then begin + aIdentifier := bits.id.Value; + result := true; + end; +end; + + +{ TSynPersistentWithPassword } + +destructor TSynPersistentWithPassword.Destroy; +begin + UniqueRawUTF8(fPassword); + FillZero(fPassword); + inherited Destroy; +end; + +class function TSynPersistentWithPassword.ComputePassword(const PlainPassword: RawUTF8; + CustomKey: cardinal): RawUTF8; +var instance: TSynPersistentWithPassword; +begin + instance := TSynPersistentWithPassword.Create; + try + instance.Key := CustomKey; + instance.SetPassWordPlain(PlainPassword); + result := instance.fPassWord; + finally + instance.Free; + end; +end; + +class function TSynPersistentWithPassword.ComputePassword(PlainPassword: pointer; + PlainPasswordLen: integer; CustomKey: cardinal): RawUTF8; +begin + result := ComputePassword(BinToBase64uri(PlainPassword,PlainPasswordLen)); +end; + +class function TSynPersistentWithPassword.ComputePlainPassword(const CypheredPassword: RawUTF8; + CustomKey: cardinal; const AppSecret: RawUTF8): RawUTF8; +var instance: TSynPersistentWithPassword; +begin + instance := TSynPersistentWithPassword.Create; + try + instance.Key := CustomKey; + instance.fPassWord := CypheredPassword; + result := instance.GetPassWordPlainInternal(AppSecret); + finally + instance.Free; + end; +end; + +function TSynPersistentWithPassword.GetPasswordFieldAddress: pointer; +begin + result := @fPassword; +end; + +function TSynPersistentWithPassword.GetKey: cardinal; +begin + if self=nil then + result := 0 else + result := fKey xor $A5abba5A; +end; + +function TSynPersistentWithPassword.GetPassWordPlain: RawUTF8; +begin + result := GetPassWordPlainInternal(''); +end; + +function TSynPersistentWithPassword.GetPassWordPlainInternal(AppSecret: RawUTF8): RawUTF8; +var value,pass: RawByteString; + usr: RawUTF8; + i,j: integer; +begin + result := ''; + if (self=nil) or (fPassWord='') then + exit; + if Assigned(TSynPersistentWithPasswordUserCrypt) then begin + if AppSecret='' then + ToText(ClassType,AppSecret); + usr := ExeVersion.User+':'; + i := PosEx(usr,fPassword); + if (i=1) or ((i>0) and (fPassword[i-1]=',')) then begin + inc(i,length(usr)); + j := PosEx(',',fPassword,i); + if j=0 then + j := length(fPassword)+1; + Base64ToBin(@fPassword[i],j-i,pass); + if pass<>'' then + result := TSynPersistentWithPasswordUserCrypt(pass,AppSecret,false); + end else begin + i := PosExChar(':',fPassword); + if i>0 then + raise ESynException.CreateUTF8('%.GetPassWordPlain unable to retrieve the '+ + 'stored value: current user is "%", but password in % was encoded for "%"', + [self,ExeVersion.User,AppSecret,copy(fPassword,1,i-1)]); + end; + end; + if result='' then begin + value := Base64ToBin(fPassWord); + SymmetricEncrypt(GetKey,value); + result := value; + end; +end; + +procedure TSynPersistentWithPassword.SetPassWordPlain(const value: RawUTF8); +var tmp: RawByteString; +begin + if self=nil then + exit; + if value='' then begin + fPassWord := ''; + exit; + end; + SetString(tmp,PAnsiChar(value),Length(value)); // private copy + SymmetricEncrypt(GetKey,tmp); + fPassWord := BinToBase64(tmp); +end; + + +{ TSynConnectionDefinition } + +constructor TSynConnectionDefinition.CreateFromJSON(const JSON: RawUTF8; + Key: cardinal); +var privateCopy: RawUTF8; + values: array[0..4] of TValuePUTF8Char; +begin + fKey := Key; + privateCopy := JSON; + JSONDecode(privateCopy,['Kind','ServerName','DatabaseName','User','Password'],@values); + fKind := values[0].ToString; + values[1].ToUTF8(fServerName); + values[2].ToUTF8(fDatabaseName); + values[3].ToUTF8(fUser); + values[4].ToUTF8(fPassWord); +end; + +function TSynConnectionDefinition.SaveToJSON: RawUTF8; +begin + result := JSONEncode(['Kind',fKind,'ServerName',fServerName, + 'DatabaseName',fDatabaseName,'User',fUser,'Password',fPassword]); +end; + + +{ TSynAuthenticationAbstract } + +constructor TSynAuthenticationAbstract.Create; +begin + fSafe.Init; + fTokenSeed := Random32; + fSessionGenerator := abs(fTokenSeed*PPtrInt(self)^); + fTokenSeed := abs(fTokenSeed*Random32); +end; + +destructor TSynAuthenticationAbstract.Destroy; +begin + fSafe.Done; + inherited; +end; + +class function TSynAuthenticationAbstract.ComputeHash(Token: Int64; + const UserName,PassWord: RawUTF8): cardinal; +begin // rough authentication - xxHash32 is less reversible than crc32c + result := xxHash32(xxHash32(xxHash32(Token,@Token,SizeOf(Token)), + pointer(UserName),length(UserName)),pointer(Password),length(PassWord)); +end; + +function TSynAuthenticationAbstract.ComputeCredential(previous: boolean; + const UserName,PassWord: RawUTF8): cardinal; +var tok: Int64; +begin + tok := GetTickCount64 div 10000; + if previous then + dec(tok); + result := ComputeHash(tok xor fTokenSeed,UserName,PassWord); +end; + +function TSynAuthenticationAbstract.CurrentToken: Int64; +begin + result := (GetTickCount64 div 10000) xor fTokenSeed; +end; + +procedure TSynAuthenticationAbstract.AuthenticateUser(const aName, aPassword: RawUTF8); +begin + raise ESynException.CreateFmt('%.AuthenticateUser() is not implemented',[self]); +end; + +procedure TSynAuthenticationAbstract.DisauthenticateUser(const aName: RawUTF8); +begin + raise ESynException.CreateFmt('%.DisauthenticateUser() is not implemented',[self]); +end; + +function TSynAuthenticationAbstract.CreateSession(const User: RawUTF8; Hash: cardinal): integer; +var password: RawUTF8; +begin + result := 0; + fSafe.Lock; + try + // check the given Hash challenge, against stored credentials + if not GetPassword(User,password) then + exit; + if (ComputeCredential(false,User,password)<>Hash) and + (ComputeCredential(true,User,password)<>Hash) then + exit; + // create the new session + repeat + result := fSessionGenerator; + inc(fSessionGenerator); + until result<>0; + AddSortedInteger(fSessions,fSessionsCount,result); + finally + fSafe.UnLock; + end; +end; + +function TSynAuthenticationAbstract.SessionExists(aID: integer): boolean; +begin + fSafe.Lock; + try + result := FastFindIntegerSorted(pointer(fSessions),fSessionsCount-1,aID)>=0; + finally + fSafe.UnLock; + end; +end; + +procedure TSynAuthenticationAbstract.RemoveSession(aID: integer); +var i: integer; +begin + fSafe.Lock; + try + i := FastFindIntegerSorted(pointer(fSessions),fSessionsCount-1,aID); + if i>=0 then + DeleteInteger(fSessions,fSessionsCount,i); + finally + fSafe.UnLock; + end; +end; + + +{ TSynAuthentication } + +constructor TSynAuthentication.Create(const aUserName,aPassword: RawUTF8); +begin + inherited Create; + fCredentials.Init(true); + if aUserName<>'' then + AuthenticateUser(aUserName,aPassword); +end; + +function TSynAuthentication.GetPassword(const UserName: RawUTF8; + out Password: RawUTF8): boolean; +var i: integer; +begin // caller did protect this method via fSafe.Lock + i := fCredentials.Find(UserName); + if i<0 then begin + result := false; + exit; + end; + password := fCredentials.List[i].Value; + result := true; +end; + +function TSynAuthentication.GetUsersCount: integer; +begin + fSafe.Lock; + try + result := fCredentials.Count; + finally + fSafe.UnLock; + end; +end; + +procedure TSynAuthentication.AuthenticateUser(const aName, aPassword: RawUTF8); +begin + fSafe.Lock; + try + fCredentials.Add(aName,aPassword); + finally + fSafe.UnLock; + end; +end; + +procedure TSynAuthentication.DisauthenticateUser(const aName: RawUTF8); +begin + fSafe.Lock; + try + fCredentials.Delete(aName); + finally + fSafe.UnLock; + end; +end; + + +{ ************ Database types and classes ************************** } + +{$ifdef FPC}{$push}{$endif} +{$WARNINGS OFF} // yes, we know there will be dead code below: we rely on it ;) + +function IsZero(const Fields: TSQLFieldBits): boolean; +var f: TPtrIntArray absolute Fields; +begin + {$ifdef CPU64} + if MAX_SQLFIELDS=64 then + result := (f[0]=0) else + if MAX_SQLFields=128 then + result := (f[0]=0) and (f[1]=0) else + if MAX_SQLFields=192 then + result := (f[0]=0) and (f[1]=0) and (f[2]=0) else + if MAX_SQLFields=256 then + result := (f[0]=0) and (f[1]=0) and (f[2]=0) and (f[3]=0) else + {$else} + if MAX_SQLFIELDS=64 then + result := (f[0]=0) and (f[1]=0) else + if MAX_SQLFields=128 then + result := (f[0]=0) and (f[1]=0) and (f[2]=0) and (f[3]=0) else + if MAX_SQLFields=192 then + result := (f[0]=0) and (f[1]=0) and (f[2]=0) and (f[3]=0) + and (f[4]=0) and (f[5]=0) else + if MAX_SQLFields=256 then + result := (f[0]=0) and (f[1]=0) and (f[2]=0) and (f[3]=0) + and (f[4]=0) and (f[5]=0) and (f[6]=0) and (f[7]=0) else + {$endif} + result := IsZero(@Fields,SizeOf(Fields)) +end; + +function IsEqual(const A,B: TSQLFieldBits): boolean; +var a_: TPtrIntArray absolute A; + b_: TPtrIntArray absolute B; +begin + {$ifdef CPU64} + if MAX_SQLFIELDS=64 then + result := (a_[0]=b_[0]) else + if MAX_SQLFields=128 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) else + if MAX_SQLFields=192 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) and (a_[2]=b_[2]) else + if MAX_SQLFields=256 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) and (a_[2]=b_[2]) and (a_[3]=b_[3]) else + {$else} + if MAX_SQLFIELDS=64 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) else + if MAX_SQLFields=128 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) and (a_[2]=b_[2]) and (a_[3]=b_[3]) else + if MAX_SQLFields=192 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) and (a_[2]=b_[2]) and (a_[3]=b_[3]) + and (a_[4]=b_[4]) and (a_[5]=b_[5]) else + if MAX_SQLFields=256 then + result := (a_[0]=b_[0]) and (a_[1]=b_[1]) and (a_[2]=b_[2]) and (a_[3]=b_[3]) + and (a_[4]=b_[4]) and (a_[5]=b_[5]) and (a_[6]=b_[6]) and (a_[7]=b_[7]) else + {$endif} + result := CompareMemFixed(@A,@B,SizeOf(TSQLFieldBits)) +end; + +procedure FillZero(var Fields: TSQLFieldBits); +begin + if MAX_SQLFIELDS=64 then + PInt64(@Fields)^ := 0 else + if MAX_SQLFields=128 then begin + PInt64Array(@Fields)^[0] := 0; + PInt64Array(@Fields)^[1] := 0; + end else + if MAX_SQLFields=192 then begin + PInt64Array(@Fields)^[0] := 0; + PInt64Array(@Fields)^[1] := 0; + PInt64Array(@Fields)^[2] := 0; + end else + if MAX_SQLFields=256 then begin + PInt64Array(@Fields)^[0] := 0; + PInt64Array(@Fields)^[1] := 0; + PInt64Array(@Fields)^[2] := 0; + PInt64Array(@Fields)^[3] := 0; + end else + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Fields,SizeOf(Fields),0); +end; + +{$ifdef FPC}{$pop}{$else}{$WARNINGS ON}{$endif} + +procedure FieldBitsToIndex(const Fields: TSQLFieldBits; var Index: TSQLFieldIndexDynArray; + MaxLength,IndexStart: integer); +var i,n: integer; + sets: array[0..MAX_SQLFIELDS-1] of TSQLFieldIndex; // to avoid memory reallocation +begin + n := 0; + for i := 0 to MaxLength-1 do + if i in Fields then begin + sets[n] := i; + inc(n); + end; + SetLength(Index,IndexStart+n); + for i := 0 to n-1 do + Index[IndexStart+i] := sets[i]; +end; + +function FieldBitsToIndex(const Fields: TSQLFieldBits; + MaxLength: integer): TSQLFieldIndexDynArray; +begin + FieldBitsToIndex(Fields,result,MaxLength); +end; + +function AddFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer; +begin + result := length(Indexes); + SetLength(Indexes,result+1); + Indexes[result] := Field; +end; + +function SearchFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer; +begin + for result := 0 to length(Indexes)-1 do + if Indexes[result]=Field then + exit; + result := -1; +end; + +procedure FieldIndexToBits(const Index: TSQLFieldIndexDynArray; var Fields: TSQLFieldBits); +var i: integer; +begin + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Fields,SizeOf(Fields),0); + for i := 0 to Length(Index)-1 do + if Index[i]>=0 then + include(Fields,Index[i]); +end; + +function FieldIndexToBits(const Index: TSQLFieldIndexDynArray): TSQLFieldBits; +begin + FieldIndexToBits(Index,result); +end; + +function DateToSQL(Date: TDateTime): RawUTF8; +begin + if Date<=0 then + result := '' else begin + SetLength(result,13); + PCardinal(pointer(result))^ := JSON_SQLDATE_MAGIC; + DateToIso8601PChar(Date,PUTF8Char(pointer(result))+3,True); + end; +end; + +function DateToSQL(Year,Month,Day: Cardinal): RawUTF8; +begin + if (Year=0) or (Month-1>11) or (Day-1>30) then + result := '' else begin + SetLength(result,13); + PCardinal(pointer(result))^ := JSON_SQLDATE_MAGIC; + DateToIso8601PChar(PUTF8Char(pointer(result))+3,True,Year,Month,Day); + end; +end; + +var + JSON_SQLDATE_MAGIC_TEXT: RawUTF8; + +function DateTimeToSQL(DT: TDateTime; WithMS: boolean): RawUTF8; +begin + if DT<=0 then + result := '' else begin + if frac(DT)=0 then + result := JSON_SQLDATE_MAGIC_TEXT+DateToIso8601(DT,true) else + if trunc(DT)=0 then + result := JSON_SQLDATE_MAGIC_TEXT+TimeToIso8601(DT,true,'T',WithMS) else + result := JSON_SQLDATE_MAGIC_TEXT+DateTimeToIso8601(DT,true,'T',WithMS); + end; +end; + +function TimeLogToSQL(const Timestamp: TTimeLog): RawUTF8; +begin + if Timestamp=0 then + result := '' else + result := JSON_SQLDATE_MAGIC_TEXT+PTimeLogBits(@Timestamp)^.Text(true); +end; + +function Iso8601ToSQL(const S: RawByteString): RawUTF8; +begin + if IsIso8601(pointer(S),length(S)) then + result := JSON_SQLDATE_MAGIC_TEXT+S else + result := ''; +end; + +function SQLToDateTime(const ParamValueWithMagic: RawUTF8): TDateTime; +begin + result := Iso8601ToDateTimePUTF8Char(PUTF8Char(pointer(ParamValueWithMagic))+3, + length(ParamValueWithMagic)-3); +end; + +const + NULL_LOW = ord('n')+ord('u')shl 8+ord('l')shl 16+ord('l')shl 24; + +function SQLParamContent(P: PUTF8Char; out ParamType: TSQLParamType; out ParamValue: RawUTF8; + out wasNull: boolean): PUTF8Char; +var PBeg: PAnsiChar; + L: integer; + c: cardinal; +begin + ParamType := sptUnknown; + wasNull := false; + result := nil; + if P=nil then + exit; + while (P^<=' ') and (P^<>#0) do inc(P); + case P^ of + '''','"': begin + P := UnQuoteSQLStringVar(P,ParamValue); + if P=nil then + exit; // not a valid quoted string (e.g. unexpected end in middle of it) + ParamType := sptText; + L := length(ParamValue)-3; + if L>0 then begin + c := PInteger(ParamValue)^ and $00ffffff; + if c=JSON_BASE64_MAGIC then begin + // ':("\uFFF0base64encodedbinary"):' format -> decode + Base64MagicDecode(ParamValue); // wrapper function to avoid temp. string + ParamType := sptBlob; + end else + if (c=JSON_SQLDATE_MAGIC) and // handle ':("\uFFF112012-05-04"):' format + IsIso8601(PUTF8Char(pointer(ParamValue))+3,L) then begin + Delete(ParamValue,1,3); // return only ISO-8601 text + ParamType := sptDateTime; // identified as Date/Time + end; + end; + end; + '-','+','0'..'9': begin // allow 0 or + in SQL + // check if P^ is a true numerical value + PBeg := pointer(P); + ParamType := sptInteger; + repeat inc(P) until not (P^ in ['0'..'9']); // check digits + if P^='.' then begin + inc(P); + if P^ in ['0'..'9'] then begin + ParamType := sptFloat; + repeat inc(P) until not (P^ in ['0'..'9']); // check fractional digits + end else begin + ParamType := sptUnknown; // invalid '23023.' value + exit; + end; + end; + if byte(P^) and $DF=ord('E') then begin + ParamType := sptFloat; + inc(P); + if P^='+' then inc(P) else + if P^='-' then inc(P); + while P^ in ['0'..'9'] do inc(P); + end; + FastSetString(ParamValue,PBeg,P-PBeg); + end; + 'n': + if PInteger(P)^=NULL_LOW then begin + inc(P,4); + wasNull := true; + end else + exit; // invalid content (only :(null): expected) + else + exit; // invalid content + end; + while (P^<=' ') and (P^<>#0) do inc(P); + if PWord(P)^<>Ord(')')+Ord(':')shl 8 then + // we expect finishing with P^ pointing at '):' + ParamType := sptUnknown else + // result<>nil only if value content in P^ + result := P+2; +end; + +function ExtractInlineParameters(const SQL: RawUTF8; + var Types: TSQLParamTypeDynArray; var Values: TRawUTF8DynArray; + var maxParam: integer; var Nulls: TSQLFieldBits): RawUTF8; +var ppBeg: integer; + P, Gen: PUTF8Char; + wasNull: boolean; +begin + maxParam := 0; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Nulls,SizeOf(Nulls),0); + ppBeg := PosEx(RawUTF8(':('),SQL,1); + if (ppBeg=0) or (PosEx(RawUTF8('):'),SQL,ppBeg+2)=0) then begin + // SQL code with no valid :(...): internal parameters -> leave maxParam=0 + result := SQL; + exit; + end; + // compute GenericSQL from SQL, converting :(...): into ? + FastSetString(result,pointer(SQL),length(SQL)); // private copy for unescape + P := pointer(result); // in-place string unescape (keep SQL untouched) + Gen := P+ppBeg-1; // Gen^ just before :( + inc(P,ppBeg+1); // P^ just after :( + repeat + Gen^ := '?'; // replace :(...): by ? + inc(Gen); + if length(Values)<=maxParam then + SetLength(Values,maxParam+16); + if length(Types)<=maxParam then + SetLength(Types,maxParam+64); + P := SQLParamContent(P,Types[maxParam],Values[maxParam],wasNull); + if P=nil then begin + maxParam := 0; + result := SQL; + exit; // any invalid parameter -> try direct SQL + end; + if wasNull then + include(Nulls,maxParam); + while (P^<>#0) and (PWord(P)^<>Ord(':')+Ord('(')shl 8) do begin + Gen^ := P^; + inc(Gen); + inc(P); + end; + if P^=#0 then + Break; + inc(P,2); + inc(maxParam); + until false; + // return generic SQL statement, with ? place-holders and params in Values[] + SetLength(result,Gen-pointer(result)); + inc(maxParam); +end; + +function InlineParameter(ID: Int64): shortstring; +begin + FormatShort(':(%):',[ID],result); +end; + +function InlineParameter(const value: RawUTF8): RawUTF8; +begin + QuotedStrJSON(value,result,':(','):'); +end; + +function SQLVarLength(const Value: TSQLVar): integer; +begin + case Value.VType of + ftBlob: + result := Value.VBlobLen; + ftUTF8: + result := StrLen(Value.VText); // fast enough for our purpose + else + result := 0; // simple/ordinal values, or ftNull + end; +end; + +{$ifndef NOVARIANTS} + +procedure VariantToSQLVar(const Input: variant; var temp: RawByteString; + var Output: TSQLVar); +var wasString: boolean; +begin + Output.Options := []; + with TVarData(Input) do + if VType=varVariant or varByRef then + VariantToSQLVar(PVariant(VPointer)^,temp,Output) else + case VType of + varEmpty, varNull: + Output.VType := ftNull; + varByte: begin + Output.VType := ftInt64; + Output.VInt64 := VByte; + end; + varInteger: begin + Output.VType := ftInt64; + Output.VInt64 := VInteger; + end; + {$ifndef DELPHI5OROLDER} + varLongWord: begin + Output.VType := ftInt64; + Output.VInt64 := VLongWord; + end; + {$endif} + varWord64, varInt64: begin + Output.VType := ftInt64; + Output.VInt64 := VInt64; + end; + varSingle: begin + Output.VType := ftDouble; + Output.VDouble := VSingle; + end; + varDouble: begin // varDate would be converted into ISO8601 by VariantToUTF8() + Output.VType := ftDouble; + Output.VDouble := VDouble; + end; + varCurrency: begin + Output.VType := ftCurrency; + Output.VInt64 := VInt64; + end; + varString: begin // assume RawUTF8 + Output.VType := ftUTF8; + Output.VText := VPointer; + end; + else // handle less current cases + if VariantToInt64(Input,Output.VInt64) then + Output.VType := ftInt64 else begin + VariantToUTF8(Input,RawUTF8(temp),wasString); + if wasString then begin + Output.VType := ftUTF8; + Output.VText := pointer(temp); + end else + Output.VType := ftNull; + end; + end; +end; + +function VariantTypeToSQLDBFieldType(const V: Variant): TSQLDBFieldType; +var tmp: TVarData; +begin + with TVarData(V) do + case VType of + varEmpty: + result := ftUnknown; + varNull: + result := ftNull; + {$ifndef DELPHI5OROLDER}varShortInt, varWord, varLongWord,{$endif} + varSmallInt, varByte, varBoolean, varInteger, varInt64, varWord64: + result := ftInt64; + varSingle,varDouble: + result := ftDouble; + varDate: + result := ftDate; + varCurrency: + result := ftCurrency; + varString: + if (VString<>nil) and (PCardinal(VString)^ and $ffffff=JSON_BASE64_MAGIC) then + result := ftBlob else + result := ftUTF8; + else + if SetVariantUnRefSimpleValue(V,tmp) then + result := VariantTypeToSQLDBFieldType(variant(tmp)) else + result := ftUTF8; + end; +end; + +{$endif NOVARIANTS} + +{ TJSONWriter } + +procedure TJSONWriter.CancelAllVoid; +const VOIDARRAY: PAnsiChar = '[]'#10; + VOIDFIELD: PAnsiChar = '{"FieldCount":0}'; +begin + CancelAll; // rewind JSON + if fExpand then // same as sqlite3_get_table() + inc(fTotalFileSize,fStream.Write(VOIDARRAY^,3)) else + inc(fTotalFileSize,fStream.Write(VOIDFIELD^,16)); +end; + +constructor TJSONWriter.Create(aStream: TStream; Expand, withID: boolean; + const Fields: TSQLFieldBits; aBufSize: integer); +begin + Create(aStream,Expand,withID,FieldBitsToIndex(Fields),aBufSize); +end; + +constructor TJSONWriter.Create(aStream: TStream; Expand, withID: boolean; + const Fields: TSQLFieldIndexDynArray; aBufSize: integer); +begin + if aStream=nil then + CreateOwnedStream else + inherited Create(aStream,aBufSize); + fExpand := Expand; + fWithID := withID; + fFields := Fields; +end; + +procedure TJSONWriter.AddColumns(aKnownRowsCount: integer); +var i: integer; +begin + if fExpand then begin + if twoForceJSONExtended in CustomOptions then + for i := 0 to High(ColNames) do + ColNames[i] := ColNames[i]+':' else + for i := 0 to High(ColNames) do + ColNames[i] := '"'+ColNames[i]+'":'; + end else begin + AddShort('{"fieldCount":'); + Add(length(ColNames)); + if aKnownRowsCount>0 then begin + AddShort(',"rowCount":'); + Add(aKnownRowsCount); + end; + AddShort(',"values":["'); + // first row is FieldNames + for i := 0 to High(ColNames) do begin + AddString(ColNames[i]); + AddNoJSONEscape(PAnsiChar('","'),3); + end; + CancelLastChar('"'); + fStartDataPosition := fStream.Position+(B-fTempBuf); + // B := buf-1 at startup -> need ',val11' position in + // "values":["col1","col2",val11,' i.e. current pos without the ',' + end; +end; + +procedure TJSONWriter.ChangeExpandedFields(aWithID: boolean; + const aFields: TSQLFieldIndexDynArray); +begin + if not Expand then + raise ESynException.CreateUTF8( + '%.ChangeExpandedFields() called with Expanded=false',[self]); + fWithID := aWithID; + fFields := aFields; +end; + +procedure TJSONWriter.EndJSONObject(aKnownRowsCount,aRowsCount: integer; + aFlushFinal: boolean); +begin + CancelLastComma; // cancel last ',' + Add(']'); + if not fExpand then begin + if aKnownRowsCount=0 then begin + AddShort(',"rowCount":'); + Add(aRowsCount); + end; + Add('}'); + end; + Add(#10); + if aFlushFinal then + FlushFinal; +end; + +procedure TJSONWriter.TrimFirstRow; +var P, PBegin, PEnd: PUTF8Char; +begin + if (self=nil) or not fStream.InheritsFrom(TMemoryStream) or + fExpand or (fStartDataPosition=0) then + exit; + // go to begin of first row + FlushToStream; // we need the data to be in fStream memory + // PBegin^=val11 in { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] } + PBegin := TMemoryStream(fStream).Memory; + PEnd := PBegin+fStream.Position; + PEnd^ := #0; // mark end of current values + inc(PBegin,fStartDataPosition+1); // +1 to include ',' of ',val11' + // jump to end of first row + P := GotoNextJSONItem(PBegin,length(ColNames)); + if P=nil then exit; // unexpected end + // trim first row data + if P^<>#0 then + {$ifdef FPC}Move{$else}MoveFast{$endif}(P^,PBegin^,PEnd-P); // erase content + fStream.Seek(PBegin-P,soCurrent); // adjust current stream position +end; + + +{ ************ Expression Search Engine ************************** } + +function ToText(r: TExprParserResult): PShortString; +begin + result := GetEnumName(TypeInfo(TExprParserResult), ord(r)); +end; + +function ToUTF8(r: TExprParserResult): RawUTF8; +begin + result := UnCamelCase(TrimLeftLowerCaseShort(ToText(r))); +end; + + +{ TExprNode } + +function TExprNode.Append(node: TExprNode): boolean; +begin + result := node <> nil; + if result then + Last.fNext := node; +end; + +constructor TExprNode.Create(nodeType: TExprNodeType); +begin + inherited Create; + fNodeType := nodeType; +end; + +destructor TExprNode.Destroy; +begin + fNext.Free; + inherited Destroy; +end; + +function TExprNode.Last: TExprNode; +begin + result := self; + while result.Next <> nil do + result := result.Next; +end; + + +{ TParserAbstract } + +constructor TParserAbstract.Create; +begin + inherited Create; + Initialize; +end; + +destructor TParserAbstract.Destroy; +begin + Clear; + inherited Destroy; +end; + +procedure TParserAbstract.Clear; +begin + fWordCount := 0; + fWords := nil; + fExpression := ''; + FreeAndNil(fFirstNode); +end; + +function TParserAbstract.ParseExpr: TExprNode; +begin + result := ParseFactor; + ParseNextCurrentWord; + if (fCurrentWord = '') or (fCurrentWord = ')') then + exit; + if IdemPropNameU(fCurrentWord, fAndWord) then begin // w1 & w2 = w1 AND w2 + ParseNextCurrentWord; + if result.Append(ParseExpr) then + result.Append(TExprNode.Create(entAnd)); + exit; + end + else if IdemPropNameU(fCurrentWord, fOrWord) then begin // w1 + w2 = w1 OR w2 + ParseNextCurrentWord; + if result.Append(ParseExpr) then + result.Append(TExprNode.Create(entOr)); + exit; + end + else if fNoWordIsAnd and result.Append(ParseExpr) then // 'w1 w2' = 'w1 & w2' + result.Append(TExprNode.Create(entAnd)); +end; + +function TParserAbstract.ParseFactor: TExprNode; +begin + if fCurrentError <> eprSuccess then + result := nil + else if IdemPropNameU(fCurrentWord, fNotWord) then begin + ParseNextCurrentWord; + result := ParseFactor; + if fCurrentError <> eprSuccess then + exit; + result.Append(TExprNode.Create(entNot)); + end + else + result := ParseTerm; +end; + +function TParserAbstract.ParseTerm: TExprNode; +begin + result := nil; + if fCurrentError <> eprSuccess then + exit; + if fCurrentWord = '(' then begin + ParseNextCurrentWord; + result := ParseExpr; + if fCurrentError <> eprSuccess then + exit; + if fCurrentWord <> ')' then begin + FreeAndNil(result); + fCurrentError := eprMissingParenthesis; + end; + end + else if fCurrentWord = '' then begin + result := nil; + fCurrentError := eprMissingFinalWord; + end + else + try // calls meta-class overriden constructor + result := fWordClass.Create(self, fCurrentWord); + fCurrentError := TExprNodeWordAbstract(result).ParseWord; + if fCurrentError <> eprSuccess then begin + FreeAndNil(result); + exit; + end; + SetLength(fWords, fWordCount + 1); + fWords[fWordCount] := TExprNodeWordAbstract(result); + inc(fWordCount); + except + FreeAndNil(result); + fCurrentError := eprInvalidExpression; + end; +end; + +function TParserAbstract.Parse(const aExpression: RawUTF8): TExprParserResult; +var + depth: integer; + n: TExprNode; +begin + Clear; + fCurrentError := eprSuccess; + fCurrent := pointer(aExpression); + ParseNextCurrentWord; + if fCurrentWord = '' then begin + result := eprNoExpression; + exit; + end; + fFirstNode := ParseExpr; + result := fCurrentError; + if result = eprSuccess then begin + depth := 0; + n := fFirstNode; + while n <> nil do begin + case n.NodeType of + entWord: begin + inc(depth); + if depth > high(fFoundStack) then begin + result := eprTooManyParenthesis; + break; + end; + end; + entOr, entAnd: + dec(depth); + end; + n := n.Next; + end; + end; + if result = eprSuccess then + fExpression := aExpression + else + Clear; + fCurrent := nil; +end; + +class function TParserAbstract.ParseError(const aExpression: RawUTF8): RawUTF8; +var + parser: TParserAbstract; + res: TExprParserResult; +begin + parser := Create; + try + res := parser.Parse(aExpression); + if res = eprSuccess then + result := '' + else + result := ToUTF8(res); + finally + parser.Free; + end; +end; + +function TParserAbstract.Execute: boolean; +var + n: TExprNode; + st: PBoolean; +begin // code below compiles very efficiently on FPC/x86-64 + st := @fFoundStack; + n := fFirstNode; + repeat + case n.NodeType of + entWord: begin + st^ := TExprNodeWordAbstract(n).fFound; + inc(st); // see eprTooManyParenthesis above to avoid buffer overflow + end; + entNot: + PAnsiChar(st)[-1] := AnsiChar(ord(PAnsiChar(st)[-1]) xor 1); + entOr: begin + dec(st); + PAnsiChar(st)[-1] := AnsiChar(st^ or boolean(PAnsiChar(st)[-1])); + end; { TODO : optimize TExprParser OR when left member is already TRUE } + entAnd: begin + dec(st); + PAnsiChar(st)[-1] := AnsiChar(st^ and boolean(PAnsiChar(st)[-1])); + end; + end; + n := n.Next; + until n = nil; + result := boolean(PAnsiChar(st)[-1]); +end; + + +{ TExprParserAbstract } + +procedure TExprParserAbstract.Initialize; +begin + fAndWord := '&'; + fOrWord := '+'; + fNotWord := '-'; + fNoWordIsAnd := true; +end; + +procedure TExprParserAbstract.ParseNextCurrentWord; +var + P: PUTF8Char; +begin + fCurrentWord := ''; + P := fCurrent; + if P = nil then + exit; + while P^ in [#1..' '] do + inc(P); + if P^ = #0 then + exit; + if P^ in PARSER_STOPCHAR then begin + FastSetString(fCurrentWord, P, 1); + fCurrent := P + 1; + end + else begin + fCurrent := P; + ParseNextWord; + end; +end; + +procedure TExprParserAbstract.ParseNextWord; +const + STOPCHAR = PARSER_STOPCHAR + [#0, ' ']; +var + P: PUTF8Char; +begin + P := fCurrent; + while not(P^ in STOPCHAR) do + inc(P); + FastSetString(fCurrentWord, fCurrent, P - fCurrent); + fCurrent := P; +end; + + +{ TExprNodeWordAbstract } + +constructor TExprNodeWordAbstract.Create(aOwner: TParserAbstract; const aWord: RawUTF8); +begin + inherited Create(entWord); + fWord := aWord; + fOwner := aOwner; +end; + + +{ TExprParserMatchNode } + +type + TExprParserMatchNode = class(TExprNodeWordAbstract) + protected + fMatch: TMatch; + function ParseWord: TExprParserResult; override; + end; + PExprParserMatchNode = ^TExprParserMatchNode; + +function TExprParserMatchNode.ParseWord: TExprParserResult; +begin + fMatch.Prepare(fWord, (fOwner as TExprParserMatch).fCaseSensitive, {reuse=}true); + result := eprSuccess; +end; + + +{ TExprParserMatch } + +constructor TExprParserMatch.Create(aCaseSensitive: boolean); +begin + inherited Create; + fCaseSensitive := aCaseSensitive; +end; + +procedure TExprParserMatch.Initialize; +begin + inherited Initialize; + fWordClass := TExprParserMatchNode; +end; + +function TExprParserMatch.Search(const aText: RawUTF8): boolean; +begin + result := Search(pointer(aText), length(aText)); +end; + +function TExprParserMatch.Search(aText: PUTF8Char; aTextLen: PtrInt): boolean; +const // rough estimation of UTF-8 characters + IS_UTF8_WORD = ['0' .. '9', 'A' .. 'Z', 'a' .. 'z', #$80 ..#$ff]; +var + P, PEnd: PUTF8Char; + n: PtrInt; +begin + P := aText; + if (P = nil) or (fWords = nil) then begin + result := false; + exit; + end; + if fMatchedLastSet > 0 then begin + n := fWordCount; + repeat + dec(n); + fWords[n].fFound := false; + until n = 0; + fMatchedLastSet := 0; + end; + PEnd := P + aTextLen; + while (P < PEnd) and (fMatchedLastSet < fWordCount) do begin + while not(P^ in IS_UTF8_WORD) do begin + inc(P); + if P = PEnd then + break; + end; + if P = PEnd then + break; + aText := P; + repeat + inc(P); + until (P = PEnd) or not(P^ in IS_UTF8_WORD); + aTextLen := P - aText; + n := fWordCount; + repeat + dec(n); + with TExprParserMatchNode(fWords[n]) do + if not fFound and fMatch.Match(aText, aTextLen) then begin + fFound := true; + inc(fMatchedLastSet); + end; + until n = 0; + end; + result := Execute; +end; + + +{ ************ Multi-Threading classes ************************** } + +{ TPendingTaskList } + +constructor TPendingTaskList.Create; +begin + inherited Create; + fTasks.InitSpecific(TypeInfo(TPendingTaskListItemDynArray),fTask,djInt64,@fCount); +end; + +function TPendingTaskList.GetTimestamp: Int64; +begin + result := {$ifdef FPCLINUX}SynFPCLinux.{$endif}GetTickCount64; +end; + +procedure TPendingTaskList.AddTask(aMilliSecondsDelayFromNow: integer; + const aTask: RawByteString); +var item: TPendingTaskListItem; + ndx: integer; +begin + item.Timestamp := GetTimestamp+aMilliSecondsDelayFromNow; + item.Task := aTask; + fSafe.Lock; + try + if fTasks.FastLocateSorted(item,ndx) then + inc(ndx); // always insert just after any existing timestamp + fTasks.FastAddSorted(ndx,item); + finally + fSafe.UnLock; + end; +end; + +procedure TPendingTaskList.AddTasks( + const aMilliSecondsDelays: array of integer; + const aTasks: array of RawByteString); +var item: TPendingTaskListItem; + i,ndx: integer; +begin + if length(aTasks)<>length(aMilliSecondsDelays) then + exit; + item.Timestamp := GetTimestamp; + fSafe.Lock; + try + for i := 0 to High(aTasks) do begin + inc(item.Timestamp,aMilliSecondsDelays[i]); + item.Task := aTasks[i]; + if fTasks.FastLocateSorted(item,ndx) then + inc(ndx); // always insert just after any existing timestamp + fTasks.FastAddSorted(ndx,item); + end; + finally + fSafe.UnLock; + end; +end; + +function TPendingTaskList.GetCount: integer; +begin + if self=nil then + result := 0 else begin + fSafe.Lock; + try + result := fCount; + finally + fSafe.UnLock; + end; + end; +end; + +function TPendingTaskList.NextPendingTask: RawByteString; +begin + result := ''; + if (self=nil) or (fCount=0) then + exit; + fSafe.Lock; + try + if fCount>0 then + if GetTimestamp>=fTask[0].Timestamp then begin + result := fTask[0].Task; + fTasks.FastDeleteSorted(0); + end; + finally + fSafe.UnLock; + end; +end; + +procedure TPendingTaskList.Clear; +begin + if (self=nil) or (fCount=0) then + exit; + fSafe.Lock; + try + fTasks.Clear; + finally + fSafe.UnLock; + end; +end; + + +{$ifndef LVCL} // LVCL does not implement TEvent + +{ TSynBackgroundThreadAbstract } + +constructor TSynBackgroundThreadAbstract.Create(const aThreadName: RawUTF8; + OnBeforeExecute,OnAfterExecute: TNotifyThreadEvent; CreateSuspended: boolean); +begin + fProcessEvent := TEvent.Create(nil,false,false,''); + fThreadName := aThreadName; + fOnBeforeExecute := OnBeforeExecute; + fOnAfterExecute := OnAfterExecute; + inherited Create(CreateSuspended{$ifdef FPC},512*1024{$endif}); // DefaultStackSize=512KB +end; + +{$ifndef HASTTHREADSTART} +procedure TSynBackgroundThreadAbstract.Start; +begin + Resume; +end; +{$endif} + +{$ifndef HASTTHREADTERMINATESET} +procedure TSynBackgroundThreadAbstract.Terminate; +begin + inherited Terminate; // FTerminated := True + TerminatedSet; +end; +{$endif} + +procedure TSynBackgroundThreadAbstract.TerminatedSet; +begin + fProcessEvent.SetEvent; // ExecuteLoop should handle Terminated flag +end; + +procedure TSynBackgroundThreadAbstract.WaitForNotExecuting(maxMS: integer); +var endtix: Int64; +begin + if fExecute = exRun then begin + endtix := SynCommons.GetTickCount64+maxMS; + repeat + Sleep(1); // wait for Execute to finish + until (fExecute <> exRun) or (SynCommons.GetTickCount64>=endtix); + end; +end; + +destructor TSynBackgroundThreadAbstract.Destroy; +begin + if fExecute = exRun then begin + Terminate; + WaitForNotExecuting(100); + end; + inherited Destroy; + FreeAndNil(fProcessEvent); +end; + +procedure TSynBackgroundThreadAbstract.SetExecuteLoopPause(dopause: boolean); +begin + if Terminated or (dopause=fExecuteLoopPause) or (fExecute=exFinished) then + exit; + fExecuteLoopPause := dopause; + fProcessEvent.SetEvent; // notify Execute main loop +end; + +procedure TSynBackgroundThreadAbstract.Execute; +begin + try + if fThreadName='' then + SetCurrentThreadName('%(%)',[self,pointer(self)]) else + SetCurrentThreadName('%',[fThreadName]); + if Assigned(fOnBeforeExecute) then + fOnBeforeExecute(self); + try + fExecute := exRun; + while not Terminated do + if fExecuteLoopPause then + FixedWaitFor(fProcessEvent,100) else + ExecuteLoop; + finally + if Assigned(fOnAfterExecute) then + fOnAfterExecute(self); + end; + finally + fExecute := exFinished; + end; +end; + +{ TSynBackgroundThreadMethodAbstract } + +constructor TSynBackgroundThreadMethodAbstract.Create(aOnIdle: TOnIdleSynBackgroundThread; + const aThreadName: RawUTF8; OnBeforeExecute,OnAfterExecute: TNotifyThreadEvent); +begin + fOnIdle := aOnIdle; // cross-platform may run Execute as soon as Create is called + fCallerEvent := TEvent.Create(nil,false,false,''); + fPendingProcessLock.Init; + inherited Create(aThreadName,OnBeforeExecute,OnAfterExecute); +end; + +destructor TSynBackgroundThreadMethodAbstract.Destroy; +begin + SetPendingProcess(flagDestroying); + fProcessEvent.SetEvent; // notify terminated + FixedWaitForever(fCallerEvent); // wait for actual termination + FreeAndNil(fCallerEvent); + inherited Destroy; + fPendingProcessLock.Done; +end; + +function TSynBackgroundThreadMethodAbstract.GetPendingProcess: TSynBackgroundThreadProcessStep; +begin + fPendingProcessLock.Lock; + result := fPendingProcessFlag; + fPendingProcessLock.UnLock; +end; + +procedure TSynBackgroundThreadMethodAbstract.SetPendingProcess(State: TSynBackgroundThreadProcessStep); +begin + fPendingProcessLock.Lock; + fPendingProcessFlag := State; + fPendingProcessLock.UnLock; +end; + +procedure TSynBackgroundThreadMethodAbstract.ExecuteLoop; +{$ifndef DELPHI5OROLDER} +var E: TObject; +{$endif} +begin + case FixedWaitFor(fProcessEvent,INFINITE) of + wrSignaled: + case GetPendingProcess of + flagDestroying: begin + fCallerEvent.SetEvent; // abort caller thread process + Terminate; // forces Execute loop ending + exit; + end; + flagStarted: + if not Terminated then + if fExecuteLoopPause then // pause -> try again later + fProcessEvent.SetEvent else + try + fBackgroundException := nil; + try + if Assigned(fOnBeforeProcess) then + fOnBeforeProcess(self); + try + Process; + finally + if Assigned(fOnAfterProcess) then + fOnAfterProcess(self); + end; + except + {$ifdef DELPHI5OROLDER} + on E: Exception do + fBackgroundException := ESynException.CreateUTF8( + 'Redirected %: "%"',[E,E.Message]); + {$else} + E := AcquireExceptionObject; + if E.InheritsFrom(Exception) then + fBackgroundException := Exception(E); + {$endif} + end; + finally + SetPendingProcess(flagFinished); + fCallerEvent.SetEvent; + end; + end; + end; +end; + +function TSynBackgroundThreadMethodAbstract.AcquireThread: TSynBackgroundThreadProcessStep; +begin + fPendingProcessLock.Lock; + try + result := fPendingProcessFlag; + if result=flagIdle then begin // we just acquired the thread! congrats! + fPendingProcessFlag := flagStarted; // atomic set "started" flag + fCallerThreadID := ThreadID; + end; + finally + fPendingProcessLock.UnLock; + end; +end; + +function TSynBackgroundThreadMethodAbstract.OnIdleProcessNotify(start: Int64): integer; +begin + result := {$ifdef FPCLINUX}SynFPCLinux.{$else}SynCommons.{$endif}GetTickCount64-start; + if result<0 then + result := MaxInt; // should happen only under XP -> ignore + if Assigned(fOnIdle) then + fOnIdle(self,result) ; +end; + +procedure TSynBackgroundThreadMethodAbstract.WaitForFinished(start: Int64; + const onmainthreadidle: TNotifyEvent); +var E: Exception; +begin + if (self=nil) or not(fPendingProcessFlag in [flagStarted, flagFinished]) then + exit; // nothing to wait for + try + if Assigned(onmainthreadidle) then begin + while FixedWaitFor(fCallerEvent,100)=wrTimeout do + onmainthreadidle(self); + end else + {$ifdef MSWINDOWS} // do process the OnIdle only if UI + if Assigned(fOnIdle) then begin + while FixedWaitFor(fCallerEvent,100)=wrTimeout do + OnIdleProcessNotify(start); + end else + {$endif} + FixedWaitForever(fCallerEvent); + if fPendingProcessFlag<>flagFinished then + ESynException.CreateUTF8('%.WaitForFinished: flagFinished?',[self]); + if fBackgroundException<>nil then begin + E := fBackgroundException; + fBackgroundException := nil; + raise E; // raise background exception in the calling scope + end; + finally + fParam := nil; + fCallerThreadID := 0; + FreeAndNil(fBackgroundException); + SetPendingProcess(flagIdle); + if Assigned(fOnIdle) then + fOnIdle(self,-1); // notify finished + end; +end; + +function TSynBackgroundThreadMethodAbstract.RunAndWait(OpaqueParam: pointer): boolean; +var start: Int64; + ThreadID: TThreadID; +begin + result := false; + ThreadID := GetCurrentThreadId; + if (self=nil) or (ThreadID=fCallerThreadID) then + // avoid endless loop when waiting in same thread (e.g. UI + OnIdle) + exit; + // 1. wait for any previous request to be finished (should not happen often) + if Assigned(fOnIdle) then + fOnIdle(self,0); // notify started + start := {$ifdef FPCLINUX}SynFPCLinux.{$else}SynCommons.{$endif}GetTickCount64; + repeat + case AcquireThread of + flagDestroying: + exit; + flagIdle: + break; // we acquired the background thread + end; + case OnIdleProcessNotify(start) of // Windows.GetTickCount64 res is 10-16 ms + 0..20: SleepHiRes(0); + 21..100: SleepHiRes(1); + 101..900: SleepHiRes(5); + else SleepHiRes(50); + end; + until false; + // 2. process execution in the background thread + fParam := OpaqueParam; + fProcessEvent.SetEvent; // notify background thread for Call pending process + WaitForFinished(start,nil); // wait for flagFinished, then set flagIdle + result := true; +end; + +function TSynBackgroundThreadMethodAbstract.GetOnIdleBackgroundThreadActive: boolean; +begin + result := (self<>nil) and Assigned(fOnIdle) and (GetPendingProcess<>flagIdle); +end; + + +{ TSynBackgroundThreadEvent } + +constructor TSynBackgroundThreadEvent.Create(aOnProcess: TOnProcessSynBackgroundThread; + aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); +begin + inherited Create(aOnIdle,aThreadName); + fOnProcess := aOnProcess; +end; + +procedure TSynBackgroundThreadEvent.Process; +begin + if not Assigned(fOnProcess) then + raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]); + fOnProcess(self,fParam); +end; + + +{ TSynBackgroundThreadMethod } + +procedure TSynBackgroundThreadMethod.Process; +var Method: ^TThreadMethod; +begin + if fParam=nil then + raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]); + Method := fParam; + Method^(); +end; + +procedure TSynBackgroundThreadMethod.RunAndWait(Method: TThreadMethod); +var Met: TMethod absolute Method; +begin + inherited RunAndWait(@Met); +end; + + +{ TSynBackgroundThreadProcedure } + +constructor TSynBackgroundThreadProcedure.Create(aOnProcess: TOnProcessSynBackgroundThreadProc; + aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); +begin + inherited Create(aOnIdle,aThreadName); + fOnProcess := aOnProcess; +end; + +procedure TSynBackgroundThreadProcedure.Process; +begin + if not Assigned(fOnProcess) then + raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]); + fOnProcess(fParam); +end; + + +{ TSynParallelProcessThread } + +procedure TSynParallelProcessThread.Process; +begin + if not Assigned(fMethod) then + exit; + fMethod(fIndexStart,fIndexStop); + fMethod := nil; +end; + +procedure TSynParallelProcessThread.Start( + Method: TSynParallelProcessMethod; IndexStart, IndexStop: integer); +begin + fMethod := Method; + fIndexStart := IndexStart; + fIndexStop := IndexStop; + fProcessEvent.SetEvent; // notify execution +end; + + +{ TSynBackgroundThreadProcess } + +constructor TSynBackgroundThreadProcess.Create(const aThreadName: RawUTF8; + aOnProcess: TOnSynBackgroundThreadProcess; aOnProcessMS: cardinal; + aOnBeforeExecute, aOnAfterExecute: TNotifyThreadEvent; + aStats: TSynMonitorClass; CreateSuspended: boolean); +begin + if not Assigned(aOnProcess) then + raise ESynException.CreateUTF8('%.Create(aOnProcess=nil)',[self]); + if aStats<>nil then + fStats := aStats.Create(aThreadName); + fOnProcess := aOnProcess; + fOnProcessMS := aOnProcessMS; + if fOnProcessMS=0 then + fOnProcessMS := INFINITE; // wait until ProcessEvent.SetEvent or Terminated + inherited Create(aThreadName,aOnBeforeExecute,aOnAfterExecute,CreateSuspended); +end; + +destructor TSynBackgroundThreadProcess.Destroy; +begin + if fExecute=exRun then begin + Terminate; + WaitForNotExecuting(10000); // expect the background task to be finished + end; + inherited Destroy; + fStats.Free; +end; + +procedure TSynBackgroundThreadProcess.ExecuteLoop; +var wait: TWaitResult; +begin + wait := FixedWaitFor(fProcessEvent,fOnProcessMS); + if not Terminated and (wait in [wrSignaled,wrTimeout]) then + if fExecuteLoopPause then // pause -> try again later + fProcessEvent.SetEvent else + try + if fStats<>nil then + fStats.ProcessStartTask; + try + fOnProcess(self,wait); + finally + if fStats<>nil then + fStats.ProcessEnd; + end; + except + on E: Exception do begin + if fStats<>nil then + fStats.ProcessErrorRaised(E); + if Assigned(fOnException) then + fOnException(E); + end; + end; +end; + + +{ TSynBackgroundTimer } + +var + ProcessSystemUse: TSystemUse; + +constructor TSynBackgroundTimer.Create(const aThreadName: RawUTF8; + aOnBeforeExecute, aOnAfterExecute: TNotifyThreadEvent; aStats: TSynMonitorClass); +begin + fTasks.Init(TypeInfo(TSynBackgroundTimerTaskDynArray),fTask); + fTaskLock.Init; + {$ifndef NOVARIANTS} + fTaskLock.LockedBool[0] := false; + {$endif} + inherited Create(aThreadName,EverySecond,1000,aOnBeforeExecute,aOnAfterExecute,aStats); +end; + +destructor TSynBackgroundTimer.Destroy; +begin + if (ProcessSystemUse<>nil) and (ProcessSystemUse.fTimer=self) then + ProcessSystemUse.fTimer := nil; // allows processing by another background timer + inherited Destroy; + fTaskLock.Done; +end; + +const + TIXPRECISION = 32; // GetTickCount64 resolution (for aOnProcessSecs=1) + +procedure TSynBackgroundTimer.EverySecond( + Sender: TSynBackgroundThreadProcess; Event: TWaitResult); +var tix: Int64; + i,f,n: integer; + t: ^TSynBackgroundTimerTask; + todo: TSynBackgroundTimerTaskDynArray; // avoid lock contention +begin + if (fTask=nil) or Terminated then + exit; + tix := {$ifdef FPCLINUX}SynFPCLinux.{$else}SynCommons.{$endif}GetTickCount64; + n := 0; + fTaskLock.Lock; + try + variant(fTaskLock.Padding[0]) := true; + try + for i := 0 to length(fTask)-1 do begin + t := @fTask[i]; + if tix>=t^.NextTix then begin + SetLength(todo,n+1); + todo[n] := t^; + inc(n); + t^.FIFO := nil; // now owned by todo[n].FIFO + t^.NextTix := tix+((t^.Secs*1000)-TIXPRECISION); + end; + end; + finally + fTaskLock.UnLock; + end; + for i := 0 to n-1 do + with todo[i] do + if FIFO<>nil then + for f := 0 to length(FIFO)-1 do + try + OnProcess(self,Event,FIFO[f]); + except + end + else + try + OnProcess(self,Event,''); + except + end; + finally + {$ifdef NOVARIANTS} + fTaskLock.Lock; + variant(fTaskLock.Padding[0]) := false; + fTaskLock.UnLock; + {$else} + fTaskLock.LockedBool[0] := false; + {$endif} + end; +end; + +function TSynBackgroundTimer.Find(const aProcess: TMethod): integer; +begin // caller should have made fTaskLock.Lock; + for result := length(fTask)-1 downto 0 do + with TMethod(fTask[result].OnProcess) do + if (Code=aProcess.Code) and (Data=aProcess.Data) then + exit; + result := -1; +end; + +procedure TSynBackgroundTimer.Enable( + aOnProcess: TOnSynBackgroundTimerProcess; aOnProcessSecs: cardinal); +var task: TSynBackgroundTimerTask; + found: integer; +begin + if (self=nil) or Terminated or not Assigned(aOnProcess) then + exit; + if aOnProcessSecs=0 then begin + Disable(aOnProcess); + exit; + end; + task.OnProcess := aOnProcess; + task.Secs := aOnProcessSecs; + task.NextTix := {$ifdef FPCLINUX}SynFPCLinux.{$else}SynCommons.{$endif}GetTickCount64+ + (aOnProcessSecs*1000-TIXPRECISION); + fTaskLock.Lock; + try + found := Find(TMethod(aOnProcess)); + if found>=0 then + fTask[found] := task else + fTasks.Add(task); + finally + fTaskLock.UnLock; + end; +end; + +function TSynBackgroundTimer.Processing: boolean; +begin + {$ifdef NOVARIANTS} + with fTaskLock.Padding[0] do + result := (VType=varBoolean) and VBoolean; + {$else} + result := fTaskLock.LockedBool[0]; + {$endif} +end; + +procedure TSynBackgroundTimer.WaitUntilNotProcessing(timeoutsecs: integer); +var timeout: Int64; +begin + if not Processing then + exit; + timeout := {$ifdef FPCLINUX}SynFPCLinux.{$else}SynCommons.{$endif}GetTickCount64+timeoutsecs*1000; + repeat + SleepHiRes(1); + until not Processing or + ({$ifdef FPCLINUX}SynFPCLinux.{$else}SynCommons.{$endif}GetTickcount64>timeout); +end; + +function TSynBackgroundTimer.ExecuteNow(aOnProcess: TOnSynBackgroundTimerProcess): boolean; +begin + result := Add(aOnProcess,#0,true); +end; + +function TSynBackgroundTimer.EnQueue(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsg: RawUTF8; aExecuteNow: boolean): boolean; +begin + result := Add(aOnProcess,aMsg,aExecuteNow); +end; + +function TSynBackgroundTimer.EnQueue(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsgFmt: RawUTF8; const Args: array of const; aExecuteNow: boolean): boolean; +var msg: RawUTF8; +begin + FormatUTF8(aMsgFmt,Args,msg); + result := Add(aOnProcess,msg,aExecuteNow); +end; + +function TSynBackgroundTimer.Add(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsg: RawUTF8; aExecuteNow: boolean): boolean; +var found: integer; +begin + result := false; + if (self=nil) or Terminated or not Assigned(aOnProcess) then + exit; + fTaskLock.Lock; + try + found := Find(TMethod(aOnProcess)); + if found>=0 then begin + with fTask[found] do begin + if aExecuteNow then + NextTix := 0; + if aMsg<>#0 then + AddRawUTF8(FIFO,aMsg); + end; + if aExecuteNow then + ProcessEvent.SetEvent; + result := true; + end; + finally + fTaskLock.UnLock; + end; +end; + +function TSynBackgroundTimer.DeQueue(aOnProcess: TOnSynBackgroundTimerProcess; + const aMsg: RawUTF8): boolean; +var found: integer; +begin + result := false; + if (self=nil) or Terminated or not Assigned(aOnProcess) then + exit; + fTaskLock.Lock; + try + found := Find(TMethod(aOnProcess)); + if found>=0 then + with fTask[found] do + result := DeleteRawUTF8(FIFO,FindRawUTF8(FIFO,aMsg)); + finally + fTaskLock.UnLock; + end; +end; + +function TSynBackgroundTimer.Disable(aOnProcess: TOnSynBackgroundTimerProcess): boolean; +var found: integer; +begin + result := false; + if (self=nil) or Terminated or not Assigned(aOnProcess) then + exit; + fTaskLock.Lock; + try + found := Find(TMethod(aOnProcess)); + if found>=0 then begin + fTasks.Delete(found); + result := true; + end; + finally + fTaskLock.UnLock; + end; +end; + +{ TSynParallelProcess } + +constructor TSynParallelProcess.Create(ThreadPoolCount: integer; const ThreadName: RawUTF8; + OnBeforeExecute, OnAfterExecute: TNotifyThreadEvent; + MaxThreadPoolCount: integer); +var i: integer; +begin + inherited Create; + if ThreadPoolCount<0 then + raise ESynParallelProcess.CreateUTF8('%.Create(%,%)',[Self,ThreadPoolCount,ThreadName]); + if ThreadPoolCount>MaxThreadPoolCount then + ThreadPoolCount := MaxThreadPoolCount; + fThreadPoolCount := ThreadPoolCount; + fThreadName := ThreadName; + SetLength(fPool,fThreadPoolCount); + for i := 0 to fThreadPoolCount-1 do + fPool[i] := TSynParallelProcessThread.Create(nil,FormatUTF8('%#%/%', + [fThreadName,i+1,fThreadPoolCount]),OnBeforeExecute,OnAfterExecute); +end; + +destructor TSynParallelProcess.Destroy; +begin + ObjArrayClear(fPool); + inherited; +end; + +procedure TSynParallelProcess.ParallelRunAndWait(const Method: TSynParallelProcessMethod; + MethodCount: integer; const OnMainThreadIdle: TNotifyEvent); +var use,t,n,perthread: integer; + error: RawUTF8; +begin + if (MethodCount<=0) or not Assigned(Method) then + exit; + if not Assigned(OnMainThreadIdle) then + if (self=nil) or (MethodCount=1) or (fThreadPoolCount=0) then begin + Method(0,MethodCount-1); // no need (or impossible) to use background thread + exit; + end; + use := MethodCount; + t := fThreadPoolCount; + if not Assigned(OnMainThreadIdle) then + inc(t); // include current thread + if use>t then + use := t; + try + // start secondary threads + perthread := MethodCount div use; + if perthread=0 then + use := 1; + n := 0; + for t := 0 to use-2 do begin + repeat + case fPool[t].AcquireThread of + flagDestroying: // should not happen + raise ESynParallelProcess.CreateUTF8( + '%.ParallelRunAndWait [%] destroying',[self,fPool[t].fThreadName]); + flagIdle: + break; // acquired (should always be the case) + end; + Sleep(1); + if Assigned(OnMainThreadIdle) then + OnMainThreadIdle(self); + until false; + fPool[t].Start(Method,n,n+perthread-1); + inc(n,perthread); + inc(fParallelRunCount); + end; + // run remaining items in the current/last thread + if n'' then + raise ESynParallelProcess.CreateUTF8('%.ParallelRunAndWait: %',[self,error]); + end; +end; + + +{ TBlockingProcess } + +constructor TBlockingProcess.Create(aTimeOutMs: integer; aSafe: PSynLocker); +begin + inherited Create(nil,false,false,''); + if aTimeOutMs<=0 then + fTimeOutMs := 3000 else // never wait for ever + fTimeOutMs := aTimeOutMs; + fSafe := aSafe; +end; + +constructor TBlockingProcess.Create(aTimeOutMs: integer); +begin + fOwnedSafe := true; + Create(aTimeOutMS,NewSynLocker); +end; + +destructor TBlockingProcess.Destroy; +begin + if fOwnedSafe then + fSafe^.DoneAndFreeMem; + inherited Destroy; +end; + +function TBlockingProcess.WaitFor: TBlockingEvent; +begin + fSafe^.Lock; + try + result := fEvent; + if fEvent in [evRaised,evTimeOut] then + exit; + fEvent := evWaiting; + finally + fSafe^.UnLock; + end; + FixedWaitFor(self,fTimeOutMs); + fSafe^.Lock; + try + if fEvent<>evRaised then + fEvent := evTimeOut; + result := fEvent; + finally + fSafe^.UnLock; + end; +end; + +function TBlockingProcess.WaitFor(TimeOutMS: integer): TBlockingEvent; +begin + if TimeOutMS <= 0 then + fTimeOutMs := 3000 // never wait for ever + else + fTimeOutMs := TimeOutMS; + result := WaitFor; +end; + +function TBlockingProcess.NotifyFinished(alreadyLocked: boolean): boolean; +begin + result := false; + if not alreadyLocked then + fSafe^.Lock; + try + if fEvent in [evRaised,evTimeOut] then + exit; // ignore if already notified + fEvent := evRaised; + SetEvent; // notify caller to unlock "WaitFor" method + result := true; + finally + fSafe^.UnLock; + end; +end; + +procedure TBlockingProcess.ResetInternal; +begin + ResetEvent; + fEvent := evNone; +end; + +function TBlockingProcess.Reset: boolean; +begin + fSafe^.Lock; + try + result := fEvent<>evWaiting; + if result then + ResetInternal; + finally + fSafe^.UnLock; + end; +end; + +procedure TBlockingProcess.Lock; +begin + fSafe^.Lock; +end; + +procedure TBlockingProcess.Unlock; +begin + fSafe^.Unlock; +end; + + +{ TBlockingProcessPoolItem } + +procedure TBlockingProcessPoolItem.ResetInternal; +begin + inherited ResetInternal; // set fEvent := evNone + fCall := 0; +end; + + +{ TBlockingProcessPool } + +constructor TBlockingProcessPool.Create(aClass: TBlockingProcessPoolItemClass); +begin + inherited Create; + if aClass=nil then + fClass := TBlockingProcessPoolItem else + fClass := aClass; + fPool := TObjectListLocked.Create(true); +end; + +const + CALL_DESTROYING = -1; + +destructor TBlockingProcessPool.Destroy; +var i: integer; + someWaiting: boolean; +begin + fCallCounter := CALL_DESTROYING; + someWaiting := false; + for i := 0 to fPool.Count-1 do + with TBlockingProcessPoolItem(fPool.List[i]) do + if Event=evWaiting then begin + SetEvent; // release WaitFor (with evTimeOut) + someWaiting := true; + end; + if someWaiting then + sleep(10); // propagate the pending evTimeOut to the WaitFor threads + fPool.Free; + inherited; +end; + +function TBlockingProcessPool.NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem; +var i: integer; + p: ^TBlockingProcessPoolItem; +begin + result := nil; + if fCallCounter=CALL_DESTROYING then + exit; + if aTimeOutMs<=0 then + aTimeOutMs := 3000; // never wait for ever + fPool.Safe.Lock; + try + p := pointer(fPool.List); + for i := 1 to fPool.Count do + if p^.Call=0 then begin + result := p^; // found a non-used entry + result.fTimeOutMs := aTimeOutMS; + break; + end else + inc(p); + if result=nil then begin + result := fClass.Create(aTimeOutMS); + fPool.Add(result); + end; + inc(fCallCounter); // 1,2,3,... + result.fCall := fCallCounter; + finally + fPool.Safe.UnLock; + end; +end; + +function TBlockingProcessPool.FromCall(call: TBlockingProcessPoolCall; + locked: boolean): TBlockingProcessPoolItem; +var i: integer; + p: ^TBlockingProcessPoolItem; +begin + result := nil; + if (fCallCounter=CALL_DESTROYING) or (call<=0) then + exit; + fPool.Safe.Lock; + try + p := pointer(fPool.List); + for i := 1 to fPool.Count do + if p^.Call=call then begin + result := p^; + if locked then + result.Lock; + exit; + end else + inc(p); + finally + fPool.Safe.UnLock; + end; +end; + +{$ifdef KYLIX3} +type + // see http://stackoverflow.com/a/3085509 about this known Kylix bug + TEventHack = class(THandleObject) // should match EXACTLY SyncObjs.pas source! + private + FEvent: TSemaphore; + FManualReset: Boolean; + end; + +function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult; +var E: TEventHack absolute Event; + procedure SetResult(res: integer); + begin + if res=0 then + result := wrSignaled else + if errno in [EAGAIN,ETIMEDOUT] then + result := wrTimeOut else begin + write(TimeOut,':',errno,' '); + result := wrError; + end; + end; +{.$define USESEMTRYWAIT} +// sem_timedwait() is slower than sem_trywait(), but consuming much less CPU +{$ifdef USESEMTRYWAIT} +var time: timespec; +{$else} +var start,current: Int64; + elapsed: LongWord; +{$endif} +begin + if Timeout=INFINITE then begin + SetResult(sem_wait(E.FEvent)); + exit; + end; + if TimeOut=0 then begin + SetResult(sem_trywait(E.FEvent)); + exit; + end; + {$ifdef USESEMTRYWAIT} + clock_gettime(CLOCK_REALTIME,time); + inc(time.tv_sec,TimeOut div 1000); + inc(time.tv_nsec,(TimeOut mod 1000)*1000000); + while time.tv_nsec>1000000000 do begin + inc(time.tv_sec); + dec(time.tv_nsec,1000000000); + end; + SetResult(sem_timedwait(E.FEvent,time)); + {$else} + start := GetTickCount64; + repeat + if sem_trywait(E.FEvent)=0 then begin + result := wrSignaled; + break; + end; + current := GetTickCount64; + elapsed := current-start; + if elapsed=0 then + sched_yield else + if elapsed>TimeOut then begin + result := wrTimeOut; + break; + end else + if elapsed<5 then + usleep(50) else + usleep(1000); + until false; + {$endif} + if E.FManualReset then begin + repeat until sem_trywait(E.FEvent)<>0; // reset semaphore state + sem_post(E.FEvent); + end; +end; + +{$else KYLIX3} // original FPC or Windows implementation is OK + +function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult; +begin + result := Event.WaitFor(TimeOut); +end; + +{$endif KYLIX3} + +procedure FixedWaitForever(Event: TEvent); +begin + FixedWaitFor(Event,INFINITE); +end; + +{$endif LVCL} // LVCL does not implement TEvent + + +{ ************ System Analysis types and classes ************************** } + + +function SystemInfoJson: RawUTF8; +var cpu,mem: RawUTF8; +begin + cpu := TSystemUse.Current(false).HistoryText(0,15,@mem); + with SystemInfo do + result := JSONEncode([ + 'host',ExeVersion.Host,'user',ExeVersion.User,'os',OSVersionText, + 'cpu',CpuInfoText,'bios',BiosInfoText, + {$ifdef MSWINDOWS}{$ifndef CPU64}'wow64',IsWow64,{$endif}{$endif MSWINDOWS} + {$ifdef CPUINTEL}'cpufeatures', LowerCase(ToText(CpuFeatures, ' ')),{$endif} + 'processcpu',cpu,'processmem',mem, + 'freemem',TSynMonitorMemory.FreeAsText, + 'disk',GetDiskPartitionsText(false,true)]); +end; + + +{ TProcessInfo } + +{$ifdef MSWINDOWS} +type + TProcessMemoryCounters = record + cb: DWORD; + PageFaultCount: DWORD; + PeakWorkingSetSize: PtrUInt; + WorkingSetSize: PtrUInt; + QuotaPeakPagedPoolUsage: PtrUInt; + QuotaPagedPoolUsage: PtrUInt; + QuotaPeakNonPagedPoolUsage: PtrUInt; + QuotaNonPagedPoolUsage: PtrUInt; + PagefileUsage: PtrUInt; + PeakPagefileUsage: PtrUInt; + end; +const + PROCESS_QUERY_LIMITED_INFORMATION = $1000; +var + // PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION + OpenProcessAccess: DWORD; + // late-binding of Windows version specific API entries + GetSystemTimes: function(var lpIdleTime, lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall; + GetProcessTimes: function(hProcess: THandle; + var lpCreationTime, lpExitTime, lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall; + GetProcessMemoryInfo: function(Process: THandle; + var ppsmemCounters: TProcessMemoryCounters; cb: DWORD): BOOL; stdcall; + EnumProcessModules: function (hProcess: THandle; var lphModule: HMODULE; cb: DWORD; + var lpcbNeeded: DWORD): BOOL; stdcall; + EnumProcesses: function(lpidProcess: PDWORD; cb: DWORD; var cbNeeded: DWORD): BOOL; stdcall; + GetModuleFileNameExW: function(hProcess: THandle; hModule: HMODULE; + lpBaseName: PWideChar; nSize: DWORD): DWORD; stdcall; + // Vista+/WS2008+ (use GetModuleFileNameEx on XP) + QueryFullProcessImageNameW: function(hProcess: THandle; dwFlags: DWORD; + lpExeName: PWideChar; lpdwSize: PDWORD): BOOL; stdcall; + +procedure InitWindowsAPI; +var Kernel, Psapi: THandle; +begin + if OSVersion>=wVista then + OpenProcessAccess := PROCESS_QUERY_LIMITED_INFORMATION else + OpenProcessAccess := PROCESS_QUERY_INFORMATION or PROCESS_VM_READ; + Kernel := GetModuleHandle(kernel32); + @GetSystemTimes := GetProcAddress(Kernel,'GetSystemTimes'); + @GetProcessTimes := GetProcAddress(Kernel,'GetProcessTimes'); + @QueryFullProcessImageNameW := GetProcAddress(Kernel,'QueryFullProcessImageNameW'); + Psapi := LoadLibrary('Psapi.dll'); + if Psapi>=32 then begin + @EnumProcesses := GetProcAddress(Psapi,'EnumProcesses'); + @GetModuleFileNameExW := GetProcAddress(Psapi,'GetModuleFileNameExW'); + @EnumProcessModules := GetProcAddress(Psapi, 'EnumProcessModules'); + @GetProcessMemoryInfo := GetProcAddress(Psapi,'GetProcessMemoryInfo'); + end; +end; + +function EnumAllProcesses(out Count: Cardinal): TCardinalDynArray; +var n: cardinal; +begin + n := 2048; + repeat + SetLength(result, n); + if EnumProcesses(pointer(result), n * 4, Count) then + Count := Count shr 2 else + Count := 0; + if Count < n then begin + if Count = 0 then + result := nil; + exit; + end; + inc(n, 1024); // (very unlikely) too small buffer + until n>8192; +end; + +function EnumProcessName(PID: Cardinal): RawUTF8; +var h: THandle; + len: DWORD; + name: array[0..4095] of WideChar; +begin + result := ''; + if PID = 0 then + exit; + h := OpenProcess(OpenProcessAccess, false, PID); + if h <> 0 then + try + if Assigned(QueryFullProcessImageNameW) then begin + len := high(name); + if QueryFullProcessImageNameW(h, 0, name, @len) then + RawUnicodeToUtf8(name, len, result); + end else + if GetModuleFileNameExW(h,0,name,high(name))<>0 then + RawUnicodeToUtf8(name, StrLenW(name), result); + finally + CloseHandle(h); + end; +end; + +function TProcessInfo.Init: boolean; +begin + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(self,SizeOf(self),0); + result := Assigned(GetSystemTimes) and Assigned(GetProcessTimes) and + Assigned(GetProcessMemoryInfo); // no monitoring API under oldest Windows +end; + +function TProcessInfo.Start: boolean; +var ftidl,ftkrn,ftusr: TFileTime; + sidl,skrn,susr: Int64; +begin + result := Assigned(GetSystemTimes) and GetSystemTimes(ftidl,ftkrn,ftusr); + if not result then + exit; + FileTimeToInt64(ftidl,sidl); + FileTimeToInt64(ftkrn,skrn); + FileTimeToInt64(ftusr,susr); + fDiffIdle := sidl-fSysPrevIdle; + fDiffKernel := skrn-fSysPrevKernel; + fDiffUser := susr-fSysPrevUser; + fDiffTotal := fDiffKernel+fDiffUser; // kernel time also includes idle time + dec(fDiffKernel, fDiffIdle); + fSysPrevIdle := sidl; + fSysPrevKernel := skrn; + fSysPrevUser := susr; +end; + +function TProcessInfo.PerProcess(PID: cardinal; Now: PDateTime; + out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean; +var + h: THandle; + ftkrn,ftusr,ftp,fte: TFileTime; + pkrn,pusr: Int64; + mem: TProcessMemoryCounters; +begin + result := false; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(Data,SizeOf(Data),0); + h := OpenProcess(OpenProcessAccess,false,PID); + if h<>0 then + try + if GetProcessTimes(h,ftp,fte,ftkrn,ftusr) then begin + if Now<>nil then + Data.Timestamp := Now^; + FileTimeToInt64(ftkrn,pkrn); + FileTimeToInt64(ftusr,pusr); + if (PrevKernel<>0) and (fDiffTotal>0) then begin + Data.Kernel := ((pkrn-PrevKernel)*100)/fDiffTotal; + Data.User := ((pusr-PrevUser)*100)/fDiffTotal; + end; + PrevKernel := pkrn; + PrevUser := pusr; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(mem,SizeOf(mem),0); + mem.cb := SizeOf(mem); + if GetProcessMemoryInfo(h,mem,SizeOf(mem)) then begin + Data.WorkKB := mem.WorkingSetSize shr 10; + Data.VirtualKB := mem.PagefileUsage shr 10; + end; + result := true; + end; + finally + CloseHandle(h); + end; +end; + +function TProcessInfo.PerSystem(out Idle,Kernel,User: currency): boolean; +begin + if fDiffTotal<=0 then begin + Idle := 0; + Kernel := 0; + User := 0; + result := false; + end else begin + Kernel := SimpleRoundTo2Digits((fDiffKernel*100)/fDiffTotal); + User := SimpleRoundTo2Digits((fDiffUser*100)/fDiffTotal); + Idle := 100-Kernel-User; // ensure sum is always 100% + result := true; + end; +end; +{$else} // not implemented yet (use /proc ?) +function TProcessInfo.Init: boolean; +begin + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(self,SizeOf(self),0); + result := false; +end; + +function TProcessInfo.Start: boolean; +begin + result := false; +end; + +function TProcessInfo.PerProcess(PID: cardinal; Now: PDateTime; + out Data: TSystemUseData; var PrevKernel, PrevUser: Int64): boolean; +begin + result := false; +end; + +function TProcessInfo.PerSystem(out Idle,Kernel,User: currency): boolean; +var P: PUTF8Char; + U, K, I, S: cardinal; +begin // see http://www.linuxhowtos.org/System/procstat.htm + result := false; + P := pointer(StringFromFile('/proc/stat', {nosize=}true)); + if P=nil then + exit; + U := GetNextItemCardinal(P,' '){=user}+GetNextItemCardinal(P,' '){=nice}; + K := GetNextItemCardinal(P,' '){=system}; + I := GetNextItemCardinal(P,' '){=idle}; + S := U+K+I; + Kernel := SimpleRoundTo2Digits((K*100)/S); + User := SimpleRoundTo2Digits((U*100)/S); + Idle := 100-Kernel-User; // ensure sum is always 100% + result := S<>0; +end; { TODO : use a diff approach for TProcessInfo.PerSystem on Linux } +{$endif MSWINDOWS} + + +{ TSystemUse } + +procedure TSystemUse.BackgroundExecute(Sender: TSynBackgroundTimer; + Event: TWaitResult; const Msg: RawUTF8); +var i: integer; + now: TDateTime; +begin + if (fProcess=nil) or (fHistoryDepth=0) or not fProcessInfo.Start then + exit; + fTimer := Sender; + now := NowUTC; + fSafe.Lock; + try + inc(fDataIndex); + if fDataIndex>=fHistoryDepth then + fDataIndex := 0; + for i := high(fProcess) downto 0 do // backwards for fProcesses.Delete(i) + with fProcess[i] do + if fProcessInfo.PerProcess(ID,@now,Data[fDataIndex],PrevKernel,PrevUser) then begin + if Assigned(fOnMeasured) then + fOnMeasured(ID,Data[fDataIndex]); + end else + if UnsubscribeProcessOnAccessError then + // if GetLastError=ERROR_INVALID_PARAMETER then + fProcesses.Delete(i); + finally + fSafe.UnLock; + end; +end; + +procedure TSystemUse.OnTimerExecute(Sender: TObject); +begin + BackgroundExecute(nil,wrSignaled,''); +end; + +constructor TSystemUse.Create(const aProcessID: array of integer; + aHistoryDepth: integer); +var i: integer; +begin + inherited Create; + fProcesses.Init(TypeInfo(TSystemUseProcessDynArray),fProcess); + {$ifdef MSWINDOWS} + if not Assigned(GetSystemTimes) or not Assigned(GetProcessTimes) or + not Assigned(GetProcessMemoryInfo) then + exit; // no system monitoring API under oldest Windows + {$else} + exit; // not implemented yet + {$endif} + if aHistoryDepth<=0 then + aHistoryDepth := 1; + fHistoryDepth := aHistoryDepth; + SetLength(fProcess,length(aProcessID)); + for i := 0 to high(aProcessID) do begin + {$ifdef MSWINDOWS} + if aProcessID[i]=0 then + fProcess[i].ID := GetCurrentProcessID else + {$endif} + fProcess[i].ID := aProcessID[i]; + SetLength(fProcess[i].Data,fHistoryDepth); + end; +end; + +constructor TSystemUse.Create(aHistoryDepth: integer); +begin + Create([0],aHistoryDepth); +end; + +procedure TSystemUse.Subscribe(aProcessID: integer); +var i,n: integer; +begin + if self=nil then + exit; + {$ifdef MSWINDOWS} + if aProcessID=0 then + aProcessID := GetCurrentProcessID; + {$endif} + fSafe.Lock; + try + n := length(fProcess); + for i := 0 to n-1 do + if fProcess[i].ID=aProcessID then + exit; // already subscribed + SetLength(fProcess,n+1); + fProcess[n].ID := aProcessID; + SetLength(fProcess[n].Data,fHistoryDepth); + finally + fSafe.UnLock; + end; +end; + +function TSystemUse.Unsubscribe(aProcessID: integer): boolean; +var i: integer; +begin + result := false; + if self=nil then + exit; + fSafe.Lock; + try + i := ProcessIndex(aProcessID); + if i>=0 then begin + fProcesses.Delete(i); + result := true; + end; + finally + fSafe.UnLock; + end; +end; + +function TSystemUse.ProcessIndex(aProcessID: integer): integer; +begin // caller should have made fSafe.Enter + {$ifdef MSWINDOWS} + if aProcessID=0 then + aProcessID := GetCurrentProcessID; + {$endif} + if self<>nil then + for result := 0 to high(fProcess) do + if fProcess[result].ID=aProcessID then + exit; + result := -1; +end; + +function TSystemUse.Data(out aData: TSystemUseData; aProcessID: integer=0): boolean; +var i: integer; +begin + result := false; + if self<>nil then begin + fSafe.Lock; + try + i := ProcessIndex(aProcessID); + if i>=0 then begin + with fProcess[i] do + aData := Data[fDataIndex]; + result := aData.Timestamp<>0; + if result then + exit; + end; + finally + fSafe.UnLock; + end; + end; + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(aData,SizeOf(aData),0); +end; + +function TSystemUse.Data(aProcessID: integer): TSystemUseData; +begin + Data(result,aProcessID); +end; + +function TSystemUse.KB(aProcessID: integer=0): cardinal; +begin + with Data(aProcessID) do + result := WorkKB+VirtualKB; +end; + +function TSystemUse.Percent(aProcessID: integer): single; +begin + with Data(aProcessID) do + result := Kernel+User; +end; + +function TSystemUse.PercentKernel(aProcessID: integer): single; +begin + result := Data(aProcessID).Kernel; +end; + +function TSystemUse.PercentUser(aProcessID: integer): single; +begin + result := Data(aProcessID).User; +end; + +function TSystemUse.PercentSystem(out Idle,Kernel,User: currency): boolean; +begin + result := fProcessInfo.PerSystem(Idle,Kernel,User); +end; + +function TSystemUse.HistoryData(aProcessID,aDepth: integer): TSystemUseDataDynArray; +var i,n,last: integer; +begin + result := nil; + if self=nil then + exit; + fSafe.Lock; + try + i := ProcessIndex(aProcessID); + if i>=0 then + with fProcess[i] do begin + n := length(Data); + last := n-1; + if (aDepth>0) and (n>aDepth) then + n := aDepth; + SetLength(result,n); // make ordered copy + for i := 0 to n-1 do begin + if i<=fDataIndex then + result[i] := Data[fDataIndex-i] else begin + result[i] := Data[last]; + dec(last); + end; + if PInt64(@result[i].Timestamp)^=0 then begin + SetLength(result,i); // truncate to latest available sample + break; + end; + end; + end; + finally + fSafe.UnLock; + end; +end; + +function TSystemUse.History(aProcessID,aDepth: integer): TSingleDynArray; +var i,n: integer; + data: TSystemUseDataDynArray; +begin + data := HistoryData(aProcessID,aDepth); + n := length(data); + SetLength(result,n); + for i := 0 to n-1 do + result[i] := data[i].Kernel+data[i].User; +end; + +class function TSystemUse.Current(aCreateIfNone: boolean): TSystemUse; +begin + if (ProcessSystemUse=nil) and aCreateIfNone then + GarbageCollectorFreeAndNil(ProcessSystemUse,TSystemUse.Create(60)); + result := ProcessSystemUse; +end; + +function TSystemUse.HistoryText(aProcessID,aDepth: integer; + aDestMemoryMB: PRawUTF8): RawUTF8; +var data: TSystemUseDataDynArray; + mem: RawUTF8; + i: integer; +begin + result := ''; + data := HistoryData(aProcessID,aDepth); + {$ifdef LINUXNOTBSD} // bsd: see VM_LOADAVG + // https://www.retro11.de/ouxr/211bsd/usr/src/lib/libc/gen/getloadavg.c.html + if data = nil then + result := StringFromFile('/proc/loadavg',{HasNoSize=}true) else + {$endif LINUXNOTBSD} + for i := 0 to high(data) do + with data[i] do begin + result := FormatUTF8('%% ',[result,TruncTo2Digits(Kernel+User)]); + if aDestMemoryMB<>nil then + mem := FormatUTF8('%% ',[mem,TruncTo2Digits(WorkKB/1024)]); + end; + result := trim(result); + if aDestMemoryMB<>nil then + aDestMemoryMB^ := trim(mem); +end; + +{$ifndef NOVARIANTS} + +function TSystemUse.HistoryVariant(aProcessID,aDepth: integer): variant; +var res: TDocVariantData absolute result; + data: TSystemUseDataDynArray; + i: integer; +begin + VarClear(result); + data := HistoryData(aProcessID,aDepth); + res.InitFast(length(data),dvArray); + for i := 0 to high(data) do + res.AddItem(TruncTo2Digits(data[i].Kernel+data[i].User)); +end; + +{$endif NOVARIANTS} + +function SortDynArrayDiskPartitions(const A,B): integer; +begin + result := SortDynArrayString(TDiskPartition(A).mounted,TDiskPartition(B).mounted); +end; + +function GetDiskPartitions: TDiskPartitions; +{$ifdef MSWINDOWS} // DeviceIoControl(IOCTL_DISK_GET_PARTITION_INFO) requires root +var drives, drive, m, n: integer; + fn, volume: TFileName; +{$else} +var mounts, fs, mnt, typ: RawUTF8; + p: PUTF8Char; + fn: TFileName; + n: integer; +{$endif} + av, fr, tot: QWord; +begin + result := nil; + n := 0; +{$ifdef MSWINDOWS} + fn := '#:\'; + drives := GetLogicalDrives; + m := 1 shl 2; + for drive := 3 to 26 do begin // retrieve partitions mounted as C..Z drives + if drives and m <> 0 then begin + fn[1] := char(64+drive); + if GetDiskInfo(fn,av,fr,tot,@volume) then begin + SetLength(result,n+1); + StringToUTF8(volume,result[n].name); + volume := ''; + result[n].mounted := fn; + result[n].size := tot; + inc(n); + end; + end; + m := m shl 1; + end; +{$else} // see https://github.com/gagern/gnulib/blob/master/lib/mountlist.c + mounts := StringFromFile({$ifdef BSD}'/etc/mtab'{$else}'/proc/self/mounts'{$endif}, + {hasnosize=}true); + p := pointer(mounts); + repeat + fs := ''; + mnt := ''; + typ := ''; + ScanUTF8(GetNextLine(p,p),'%S %S %S',[@fs,@mnt,@typ]); + if (fs<>'') and (fs<>'rootfs') and (IdemPCharArray(pointer(fs),['/DEV/LOOP'])<0) and + (mnt<>'') and (mnt<>'/mnt') and (typ<>'') and + (IdemPCharArray(pointer(mnt),['/PROC/','/SYS/','/RUN/'])<0) and + (FindPropName(['autofs','proc','subfs','debugfs','devpts','fusectl','mqueue', + 'rpc-pipefs','sysfs','devfs','kernfs','ignore','none','tmpfs','securityfs', + 'ramfs','rootfs','devtmpfs','hugetlbfs','iso9660'],typ)<0) then begin + fn := UTF8ToString(mnt); + if GetDiskInfo(fn,av,fr,tot) and (tot>1 shl 20) then begin + //writeln('fs=',fs,' mnt=',mnt,' typ=',typ, ' av=',KB(av),' fr=',KB(fr),' tot=',KB(tot)); + SetLength(result,n+1); + result[n].name := fs; + result[n].mounted := fn; + result[n].size := tot; + inc(n); + end; + end; + until p=nil; + DynArray(TypeInfo(TDiskPartitions),result).Sort(SortDynArrayDiskPartitions); + {$endif} +end; + +var + _DiskPartitions: TDiskPartitions; + +function GetDiskPartitionsText(nocache, withfreespace, nospace: boolean): RawUTF8; +const F: array[boolean] of RawUTF8 = ({$ifdef MSWINDOWS}'%: % (% / %)', '%: % (%/%)' + {$else}'% % (% / %)', '% % (%/%)'{$endif}); +var i: integer; + parts: TDiskPartitions; + function GetInfo(var p: TDiskPartition): shortstring; + var av, fr, tot: QWord; + begin + if not withfreespace or not GetDiskInfo(p.mounted,av,fr,tot) then + {$ifdef MSWINDOWS} + FormatShort('%: % (%)',[p.mounted[1],p.name,KB(p.size,nospace)],result) else + FormatShort(F[nospace],[p.mounted[1],p.name,KB(p.size,nospace)],result); + {$else} + FormatShort('% % (%)',[p.mounted,p.name,KB(p.size,nospace)],result) else + FormatShort(F[nospace],[p.mounted,p.name,KB(fr,nospace),KB(tot,nospace)],result); + {$endif} + end; +begin + if (_DiskPartitions=nil) or nocache then + _DiskPartitions := GetDiskPartitions; + parts := _DiskPartitions; + if parts=nil then + result := '' else + ShortStringToAnsi7String(GetInfo(parts[0]),result); + for i := 1 to high(parts) do + result := FormatUTF8('%, %',[result,GetInfo(parts[i])]); +end; + +{ TSynMonitorMemory } + +constructor TSynMonitorMemory.Create(aTextNoSpace: boolean); +begin + FAllocatedUsed := TSynMonitorOneSize.Create(aTextNoSpace); + FAllocatedReserved := TSynMonitorOneSize.Create(aTextNoSpace); + FPhysicalMemoryFree := TSynMonitorOneSize.Create(aTextNoSpace); + FVirtualMemoryFree := TSynMonitorOneSize.Create(aTextNoSpace); + FPagingFileTotal := TSynMonitorOneSize.Create(aTextNoSpace); + FPhysicalMemoryTotal := TSynMonitorOneSize.Create(aTextNoSpace); + FVirtualMemoryTotal := TSynMonitorOneSize.Create(aTextNoSpace); + FPagingFileFree := TSynMonitorOneSize.Create(aTextNoSpace); +end; + +destructor TSynMonitorMemory.Destroy; +begin + FAllocatedReserved.Free; + FAllocatedUsed.Free; + FPhysicalMemoryFree.Free; + FVirtualMemoryFree.Free; + FPagingFileTotal.Free; + FPhysicalMemoryTotal.Free; + FVirtualMemoryTotal.Free; + FPagingFileFree.Free; + inherited Destroy; +end; + +class function TSynMonitorMemory.FreeAsText(nospace: boolean): ShortString; +const F: array[boolean] of RawUTF8 = ('% / %', '%/%'); +begin + with TSynMonitorMemory.Create(nospace) do + try + RetrieveMemoryInfo; + FormatShort(F[nospace],[fPhysicalMemoryFree.Text,fPhysicalMemoryTotal.Text],result); + finally + Free; + end; +end; + +var + PhysicalAsTextCache: TShort16; // this value doesn't change usually + +class function TSynMonitorMemory.PhysicalAsText(nospace: boolean): TShort16; +begin + if PhysicalAsTextCache='' then + with TSynMonitorMemory.Create(nospace) do + try + PhysicalAsTextCache := PhysicalMemoryTotal.Text; + finally + Free; + end; + result := PhysicalAsTextCache; +end; + +class function TSynMonitorMemory.ToJSON: RawUTF8; +begin + with TSynMonitorMemory.Create(false) do + try + RetrieveMemoryInfo; + FormatUTF8('{Allocated:{reserved:%,used:%},Physical:{total:%,free:%,percent:%},'+ + {$ifdef MSWINDOWS}'Virtual:{total:%,free:%},'+{$endif}'Paged:{total:%,free:%}}', + [fAllocatedReserved.Bytes shr 10,fAllocatedUsed.Bytes shr 10, + fPhysicalMemoryTotal.Bytes shr 10,fPhysicalMemoryFree.Bytes shr 10, fMemoryLoadPercent, + {$ifdef MSWINDOWS}fVirtualMemoryTotal.Bytes shr 10,fVirtualMemoryFree.Bytes shr 10,{$endif} + fPagingFileTotal.Bytes shr 10,fPagingFileFree.Bytes shr 10],result); + finally + Free; + end; +end; + +{$ifndef NOVARIANTS} +class function TSynMonitorMemory.ToVariant: variant; +begin + result := _JsonFast(ToJSON); +end; +{$endif} + +function TSynMonitorMemory.GetAllocatedUsed: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FAllocatedUsed; +end; + +function TSynMonitorMemory.GetAllocatedReserved: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FAllocatedReserved; +end; + +function TSynMonitorMemory.GetMemoryLoadPercent: integer; +begin + RetrieveMemoryInfo; + result := FMemoryLoadPercent; +end; + +function TSynMonitorMemory.GetPagingFileFree: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FPagingFileFree; +end; + +function TSynMonitorMemory.GetPagingFileTotal: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FPagingFileTotal; +end; + +function TSynMonitorMemory.GetPhysicalMemoryFree: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FPhysicalMemoryFree; +end; + +function TSynMonitorMemory.GetPhysicalMemoryTotal: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FPhysicalMemoryTotal; +end; + +function TSynMonitorMemory.GetVirtualMemoryFree: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FVirtualMemoryFree; +end; + +function TSynMonitorMemory.GetVirtualMemoryTotal: TSynMonitorOneSize; +begin + RetrieveMemoryInfo; + result := FVirtualMemoryTotal; +end; + +{$ifdef MSWINDOWS} +{$ifndef UNICODE} // missing API for oldest Delphi +type + DWORDLONG = Int64; + TMemoryStatusEx = record + dwLength: DWORD; + dwMemoryLoad: DWORD; + ullTotalPhys: DWORDLONG; + ullAvailPhys: DWORDLONG; + ullTotalPageFile: DWORDLONG; + ullAvailPageFile: DWORDLONG; + ullTotalVirtual: DWORDLONG; + ullAvailVirtual: DWORDLONG; + ullAvailExtendedVirtual: DWORDLONG; + end; + +// information about the system's current usage of both physical and virtual memory +function GlobalMemoryStatusEx(var lpBuffer: TMemoryStatusEx): BOOL; + stdcall; external kernel32; +{$endif} +{$endif} + +function GetMemoryInfo(out info: TMemoryInfo; withalloc: boolean): boolean; +{$ifdef WITH_FASTMM4STATS} +var Heap: TMemoryManagerState; + sb: integer; +{$endif} +{$ifdef MSWINDOWS} +var global: TMemoryStatusEx; + {$ifdef FPC}mem: TProcessMemoryCounters;{$endif} +begin + FillCharFast(global,SizeOf(global),0); + global.dwLength := SizeOf(global); + result := GlobalMemoryStatusEx(global); + info.percent := global.dwMemoryLoad; + info.memtotal := global.ullTotalPhys; + info.memfree := global.ullAvailPhys; + info.filetotal := global.ullTotalPageFile; + info.filefree := global.ullAvailPageFile; + info.vmtotal := global.ullTotalVirtual; + info.vmfree := global.ullAvailVirtual; + {$ifdef FPC} // GetHeapStatus is only about current thread -> use WinAPI + if withalloc and Assigned(GetProcessMemoryInfo) then begin + FillcharFast(mem,SizeOf(mem),0); + mem.cb := SizeOf(mem); + GetProcessMemoryInfo(GetCurrentProcess,mem,SizeOf(mem)); + info.allocreserved := mem.PeakWorkingSetSize; + info.allocused := mem.WorkingSetSize; + end; + {$endif FPC} +{$else} +{$ifdef BSD} +begin + {$ifdef FPC}FillChar{$else}FillCharFast{$endif}(info,SizeOf(info),0); + info.memtotal := fpsysctlhwint({$ifdef DARWIN}HW_MEMSIZE{$else}HW_PHYSMEM{$endif}); + info.memfree := info.memtotal-fpsysctlhwint(HW_USERMEM); + if info.memtotal<>0 then // avoid div per 0 exception + info.percent := ((info.memtotal-info.memfree)*100)div info.memtotal; +{$else} +var si: TSysInfo; // Linuxism + P: PUTF8Char; + {$ifdef FPC}mu: cardinal{$else}const mu=1{$endif}; +begin + FillCharFast(info,SizeOf(info),0); + {$ifdef FPC} + result := SysInfo(@si)=0; + mu := si.mem_unit; + {$else} + result := SysInfo(si)=0; // some missing fields in Kylix' Libc + {$endif} + if si.totalram<>0 then // avoid div per 0 exception + info.percent := ((si.totalram-si.freeram)*100)div si.totalram; + info.memtotal := si.totalram*mu; + info.memfree := si.freeram*mu; + info.filetotal := si.totalswap*mu; + info.filefree := si.freeswap*mu; + if withalloc then begin + // virtual memory information is not available under Linux + P := pointer(StringFromFile('/proc/self/statm',{hasnosize=}true)); + info.allocreserved := GetNextItemCardinal(P,' ')*SystemInfo.dwPageSize; // VmSize + info.allocused := GetNextItemCardinal(P,' ')*SystemInfo.dwPageSize; // VmRSS + end; + // GetHeapStatus is only about current thread -> use /proc/[pid]/statm +{$endif BSD} +{$endif MSWINDOWS} +{$ifdef WITH_FASTMM4STATS} // override OS information by actual FastMM4 + if withalloc then begin + GetMemoryManagerState(Heap); // direct raw FastMM4 access + info.allocused := Heap.TotalAllocatedMediumBlockSize+Heap.TotalAllocatedLargeBlockSize; + info.allocreserved := Heap.ReservedMediumBlockAddressSpace+Heap.ReservedLargeBlockAddressSpace; + for sb := 0 to high(Heap.SmallBlockTypeStates) do + with Heap.SmallBlockTypeStates[sb] do begin + inc(info.allocused,UseableBlockSize*AllocatedBlockCount); + inc(info.allocreserved,ReservedAddressSpace); + end; + end; +{$endif WITH_FASTMM4STATS} +end; + +procedure TSynMonitorMemory.RetrieveMemoryInfo; +var tix: cardinal; + info: TMemoryInfo; +begin + tix := GetTickCount64 shr 7; // allow 128 ms resolution for updates + if fLastMemoryInfoRetrievedTix<>tix then begin + fLastMemoryInfoRetrievedTix := tix; + if not GetMemoryInfo(info,{withalloc=}true) then + exit; + FMemoryLoadPercent := info.percent; + FPhysicalMemoryTotal.Bytes := info.memtotal; + FPhysicalMemoryFree.Bytes := info.memfree; + FPagingFileTotal.Bytes := info.filetotal; + FPagingFileFree.Bytes := info.filefree; + FVirtualMemoryTotal.Bytes := info.vmtotal; + FVirtualMemoryFree.Bytes := info.vmfree; + FAllocatedReserved.Bytes := info.allocreserved; + FAllocatedUsed.Bytes := info.allocused; + end; +end; + + +{ TSynMonitorDisk } + +constructor TSynMonitorDisk.Create; +begin + fAvailableSize := TSynMonitorOneSize.Create({nospace=}false); + fFreeSize := TSynMonitorOneSize.Create({nospace=}false); + fTotalSize := TSynMonitorOneSize.Create({nospace=}false); +end; + +destructor TSynMonitorDisk.Destroy; +begin + fAvailableSize.Free; + fFreeSize.Free; + fTotalSize.Free; + inherited; +end; + +function TSynMonitorDisk.GetName: TFileName; +begin + RetrieveDiskInfo; + result := fName; +end; + +function TSynMonitorDisk.GetAvailable: TSynMonitorOneSize; +begin + RetrieveDiskInfo; + result := fAvailableSize; +end; + +function TSynMonitorDisk.GetFree: TSynMonitorOneSize; +begin + RetrieveDiskInfo; + result := fFreeSize; +end; + +function TSynMonitorDisk.GetTotal: TSynMonitorOneSize; +begin + RetrieveDiskInfo; + result := fTotalSize; +end; + +class function TSynMonitorDisk.FreeAsText: RawUTF8; +var name: TFileName; + avail,free,total: QWord; +begin + GetDiskInfo(name,avail,free,total); + FormatUTF8('% % / %',[name, KB(free),KB(total)],result); +end; + +{$ifdef MSWINDOWS} +function GetDiskFreeSpaceExA(lpDirectoryName: PAnsiChar; + var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes, + lpTotalNumberOfFreeBytes: QWord): LongBool; stdcall; external kernel32; +function GetDiskFreeSpaceExW(lpDirectoryName: PWideChar; + var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes, + lpTotalNumberOfFreeBytes: QWord): LongBool; stdcall; external kernel32; +function DeviceIoControl(hDevice: THandle; dwIoControlCode: DWORD; lpInBuffer: Pointer; + nInBufferSize: DWORD; lpOutBuffer: Pointer; nOutBufferSize: DWORD; + var lpBytesReturned: DWORD; lpOverlapped: POverlapped): BOOL; stdcall; external kernel32; +{$endif} + +function GetDiskInfo(var aDriveFolderOrFile: TFileName; + out aAvailableBytes, aFreeBytes, aTotalBytes: QWord + {$ifdef MSWINDOWS}; aVolumeName: PFileName = nil{$endif}): boolean; +{$ifdef MSWINDOWS} +var tmp: array[0..MAX_PATH-1] of Char; + dummy,flags: DWORD; + dn: TFileName; +begin + if aDriveFolderOrFile='' then + aDriveFolderOrFile := SysUtils.UpperCase(ExtractFileDrive(ExeVersion.ProgramFilePath)); + dn := aDriveFolderOrFile; + if (dn<>'') and (dn[2]=':') and (dn[3]=#0) then + dn := dn+'\'; + if (aVolumeName<>nil) and (aVolumeName^='') then begin + tmp[0] := #0; + GetVolumeInformation(pointer(dn),tmp,MAX_PATH,nil,dummy,flags,nil,0); + aVolumeName^ := tmp; + end; + result := {$ifdef UNICODE}GetDiskFreeSpaceExW{$else}GetDiskFreeSpaceExA{$endif}( + pointer(dn),aAvailableBytes,aTotalBytes,aFreeBytes); +{$else} +{$ifdef KYLIX3} +var fs: TStatFs64; + h: THandle; +begin + if aDriveFolderOrFile='' then + aDriveFolderOrFile := '.'; + h := FileOpen(aDriveFolderOrFile,fmShareDenyNone); + result := fstatfs64(h,fs)=0; + FileClose(h); + aAvailableBytes := fs.f_bavail*fs.f_bsize; + aFreeBytes := aAvailableBytes; + aTotalBytes := fs.f_blocks*fs.f_bsize; +{$endif} +{$ifdef FPC} +var fs: tstatfs; +begin + if aDriveFolderOrFile='' then + aDriveFolderOrFile := '.'; + result := fpStatFS(aDriveFolderOrFile,@fs)=0; + aAvailableBytes := QWord(fs.bavail)*QWord(fs.bsize); + aFreeBytes := aAvailableBytes; // no user Quota involved here + aTotalBytes := QWord(fs.blocks)*QWord(fs.bsize); +{$endif FPC} +{$endif MSWINDOWS} +end; + +procedure TSynMonitorDisk.RetrieveDiskInfo; +var tix: cardinal; +begin + tix := GetTickCount64 shr 7; // allow 128 ms resolution for updates + if fLastDiskInfoRetrievedTix<>tix then begin + fLastDiskInfoRetrievedTix := tix; + GetDiskInfo(fName,PQWord(@fAvailableSize.Bytes)^,PQWord(@fFreeSize.Bytes)^, + PQWord(@fTotalSize.Bytes)^{$ifdef MSWINDOWS},@fVolumeName{$endif}); + end; +end; + + +initialization + Assert(SizeOf(TSynTableFieldType)=1); // as expected by TSynTableFieldProperties + Assert(SizeOf(TSynTableFieldOptions)=1); + {$ifndef NOVARIANTS} + Assert(SizeOf(TSynTableData)=SizeOf(TVarData)); + {$endif NOVARIANTS} + Assert(SizeOf(THTab)=$40000*3); // 786,432 bytes + Assert(SizeOf(TSynUniqueIdentifierBits)=SizeOf(TSynUniqueIdentifier)); + SetLength(JSON_SQLDATE_MAGIC_TEXT,3); + PCardinal(pointer(JSON_SQLDATE_MAGIC_TEXT))^ := JSON_SQLDATE_MAGIC; + {$ifdef MSWINDOWS} + InitWindowsAPI; + {$endif MSWINDOWS} + TTextWriter.RegisterCustomJSONSerializerFromText([ + TypeInfo(TDiskPartitions), + 'name:RawUTF8 mounted:string size:QWord', + TypeInfo(TSystemUseDataDynArray), + 'Timestamp:TDateTime Kernel,User:single WorkDB,VirtualKB:cardinal']); +end. + diff --git a/ThirdParty/mORMot/Source/Synopse.inc b/ThirdParty/mORMot/Source/Synopse.inc index f390fe69..47dd30a1 100644 --- a/ThirdParty/mORMot/Source/Synopse.inc +++ b/ThirdParty/mORMot/Source/Synopse.inc @@ -20,7 +20,7 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2018 + Portions created by the Initial Developer are Copyright (C) 2019 the Initial Developer. All Rights Reserved. Contributor(s): @@ -109,13 +109,6 @@ // to access 'VarCopyProc' from unit 'SynCommons'" // - shall be set at the package options level, and left untouched by default -{$define WITHLOG} -// if defined, logging will be supported via the TSQLLog family -// - should be left defined: TSQLog.Family.Level default setting won't log -// anything, so there won't be any noticeable performance penalty to have -// this WITHLOG conditional defined, which is expected by high-level units -// of the framework, like DDD or UI - {.$define DOPATCHTRTL} // if defined, the low-level patches made to RecordCopy() low-level function // as defined in SynCommons.pas will be applied (if applicable to your Delphi @@ -147,18 +140,33 @@ // which will use BT[mem] opcodes, which are slow on Intel, but fast on AMD // (with the Delphi x86 compiler, may not be the case for LLVM or FPC) +{.$define FORCE_STRSSE42} +// sse4.2 string instructions may read up to 16 bytes after the actual end buffer +// -> define this if you want StrLen/StrComp/strspn/strcspn to use SSE4.2 opcodes +// but you would eventually experiment weird random GPF in your project, raising +// unexpected SIGABRT/SIGSEGV under POSIX system: so is disabled below for our +// LINUX conditional - and use at your own risk under Windows! + +{.$define DISABLE_SSE42} +// if defined, no SSE4.2 nor AES-NI instruction will be used, i.e. disable +// FORCE_STRSSE42 and all crc32c opcodes - is set for FPC DARWIN target + {.$define WITH_ERMS} // you may define this to enable REP MOVSB/STOSB for Fillchar/Move if cfERMS // flag is available in Intel's CpuFeatures -// -> disabled by default, since in practice it is much slower for small blocks +// -> disabled by default, since in practice it is (much) slower for small blocks {.$define NOXPOWEREDNAME} // define this to avoid sending "X-Powered-By: Synopse mORMot" HTTP header {.$define SQLVIRTUALLOGS} // enable low-level logging of SQlite3 virtual table query planner costs -// -> to be used only for internal debugging +// -> to be defined only for internal debugging +{.$define DDDNOSYNDB} +// SynDB / external SQL DB won't be linked to the executable by dddInfraSettings +{.$define DDDNOMONGODB} +// Mongo DB client won't be linked to the executable by dddInfraSettings {$ifdef FPC} @@ -200,7 +208,7 @@ {$define HASUINT64} {$define HASINLINENOTX86} {$define NODELPHIASM} // ignore low-level System.@LStrFromPCharLen calls - {$define HASAESNI} + {$define HASAESNI} // should be commented to test project with valgrind {$define HASTTHREADSTART} {$define HASINTERFACEASTOBJECT} {$define EXTENDEDTOSTRING_USESTR} // FloatToText uses str() in FPC @@ -274,7 +282,11 @@ {$define LINUX} // not true, but a POSIX/BSD system {$ifdef DARWIN} {$define FPCSQLITE3STATIC} // we supply Darwin static libs - {$define FPC_PIC} // may have not be defined by the compiler options + {$ifdef CPUX86} + {$define FPC_PIC} // may have not be defined by the compiler options + {$endif} + {$undef FORCE_STRSSE42} // fails otherwise for sure + {$define ABSOLUTEPASCAL} // NO asm nor redirection (until stabilized) {$else} {$define BSDNOTDARWIN} {$endif} @@ -284,6 +296,10 @@ {$endif} {$endif} + {$ifdef LINUX} + {$undef FORCE_STRSSE42} // avoid fatal SIGABRT/SIGSEGV on POSIX systems + {$define FPCLINUX} + {$endif} {$ifdef FPC_PIC} {$define PUREPASCAL} // most asm code is not PIC-safe with global constants {$endif} @@ -305,6 +321,9 @@ {$define CPUINTEL} {$define FPC_CPUINTEL} {$ASMMODE INTEL} // as Delphi expects + {$ifndef FPC_PIC} + {$define CPUX86NOTPIC} // use "absolute" instead of local register + {$endif FPC_PIC} {$endif CPUX86} {$endif CPU64} @@ -394,6 +413,7 @@ {$define CPU32DELPHI} {$undef CPU64} {$define CPUX86} // for compatibility with older versions of Delphi + {$define CPUX86NOTPIC} // use "absolute" instead of local register {$endif CPUX64} {$IFDEF CONDITIONALEXPRESSIONS} // Delphi 6 or newer @@ -442,6 +462,9 @@ {$define HASINLINENOTX86} {$define HASREGION} {$define HASFASTMM4} + // you can define this so that GetMemoryInfo/TSynMonitorMemory returns + // low-level FastMM4 information + {.$define WITH_FASTMM4STATS} {$ifend} {$ifdef VER180} {$define ISDELPHI20062007} // to circumvent some specific bugs @@ -582,10 +605,29 @@ {$endif} {$endif} {$endif} + {$ifdef DISABLE_SSE42} + {$undef FORCE_STRSSE42} + {$endif DISABLE_SSE42} {$else} {$undef HASAESNI} // AES-NI is an Intel-specific feature + {$define ABSOLUTEPASCALORNOTINTEL} {$endif CPUINTEL} +{$ifdef ABSOLUTEPASCAL} + {$define ABSOLUTEORPUREPASCAL} + {$define ABSOLUTEPASCALORNOTINTEL} +{$endif ABSOLUTEPASCAL} +{$ifdef PUREPASCAL} + {$define ABSOLUTEORPUREPASCAL} +{$endif PUREPASCAL} + +{$define WITHLOG} +// if defined, logging will be supported via the TSQLLog family +// - should be left defined: TSQLog.Family.Level default setting won't log +// anything, so there won't be any noticeable performance penalty to have +// this WITHLOG conditional defined, which is expected by high-level part +// of the framework, like DDD or UI units + {$ifdef FPC} {$ifndef FPCSQLITE3STATIC} // see above about this FPC-specific conditional {$define NOSQLITE3STATIC} @@ -595,12 +637,12 @@ {$ifdef CPU64} {$define NOSQLITE3STATIC} {$endif} -{$endif} +{$endif FPC} {$ifdef NOSQLITE3STATIC} // our proprietary crypto expects a statically linked custom sqlite3.c {$define NOSQLITE3ENCRYPT} -{$endif} +{$endif NOSQLITE3STATIC} {$ifdef MSWINDOWS} {$define USEWININET} // publish TWinINet/TWinHttp/TWinHttpAPI classes @@ -613,6 +655,11 @@ {$define USELIBCURL} // for Android, consider using https://github.com/gcesarmza/curl-android-ios // static libraries and force USELIBCURL in the project conditionals - {$endif} -{$endif} + {$endif ANDROID} +{$endif MSWINDOWS} + +{$ifdef USELIBCURL} + {.$define LIBCURLMULTI} + // enable https://curl.haxx.se/libcurl/c/libcurl-multi.html interface +{$endif USELIBCURL} diff --git a/ThirdParty/mORMot/Source/SynopseCommit.inc b/ThirdParty/mORMot/Source/SynopseCommit.inc index b0169bbb..db0c327e 100644 --- a/ThirdParty/mORMot/Source/SynopseCommit.inc +++ b/ThirdParty/mORMot/Source/SynopseCommit.inc @@ -1 +1 @@ -'1.18.4886' +'1.18.5231' diff --git a/Utils/Source/MARScmd/MARScmd_VCL.dproj b/Utils/Source/MARScmd/MARScmd_VCL.dproj index 08d82c1d..e7859151 100644 --- a/Utils/Source/MARScmd/MARScmd_VCL.dproj +++ b/Utils/Source/MARScmd/MARScmd_VCL.dproj @@ -1,7 +1,7 @@  {5AC1AE2B-2715-4744-81F3-0E0B30BD6CAF} - 18.5 + 19.0 VCL MARScmd_VCL.dpr True @@ -157,12 +157,20 @@ classes 1
+ + classes + 1 +
res\xml 1 + + res\xml + 1 + @@ -175,96 +183,242 @@ 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\values 1 + + res\values + 1 + res\values-v21 1 + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + res\drawable 1 + + res\drawable + 1 + res\drawable-xxhdpi 1 + + res\drawable-xxhdpi + 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 + @@ -353,6 +507,9 @@ 0 + + 0 + 0 @@ -385,6 +542,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -396,6 +564,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -407,6 +608,71 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -418,6 +684,136 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -429,6 +825,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + 1 @@ -451,10 +857,55 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 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 + + 1 + + 1 + @@ -495,6 +946,16 @@ 1 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + 1 @@ -547,6 +1008,10 @@ library\lib\armeabi-v7a 1 + + library\lib\arm64-v8a + 1 + 1 @@ -571,6 +1036,12 @@ 0 + + + library\lib\armeabi-v7a + 1 + + 1 @@ -608,6 +1079,7 @@ + True diff --git a/Utils/Source/MARScmd/MARScmd_VCL.res b/Utils/Source/MARScmd/MARScmd_VCL.res index 68a6af8fd4a304057d81d36cb26353195c76edfe..0e5c8edcb5098d6c6bb9c3dcb310dfaacf62a24c 100644 GIT binary patch literal 112124 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 F{U203I6VLW literal 59532 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

r7T*th>_Ima}&(;Bd{#ms?SFTOQ0=xfB&lo?? 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:cq(pbyEp(-a#wfzFcJFPsut;Cyjkgy;j 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

3*9URd0D-f%tN7!+OD6P_DdCn9kYxf+l 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

-`t(k_O)n$apTWysK=%smW5d9}?6 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∾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Ò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

-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 gb2b&Y$6A?$ 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 vp2#VNumi>sz@ZI!kj%_=p8ZH@vSvtn;(#CZ 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%0DjP>e(t%*dp!Z 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�`#mltCT{{90if}%%g=9dx4W#cs8<5 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�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

u4>{6Y5 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%0D^LJ@4!~J~vV!zI)O&r$LjYiX1osN~tpK3q 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

_fznw#ry~kY)VQ)fGA!{bFW7pE%OjNQGD-%R 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

5>mC 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%0Dg%2leM~s7 z5b#$%Of?ELE?>=wu}N4uP$A~cNUAr)YGArDR4R3{7qt39I=VeC>H)(Ydg3bHK*y5g zd$gn{Ms@j#Us6gFR5P&%4Wy~^z{Es&f*|@&*5&xROG?eZ1gRy7?MZ<3E|Cnxt8D=}>ouEG26CHnHufcIfCXZeW7!MAwFLeMSFLsr-V)$A_nBk8M%U%L(R; zZkEaY-E~&{U|a1#B|}q7IzAYea?wrrWOxFxb(7^xeZm~;nJH8g&SQn)EDT^7=%`sE z=|eVtHeq6XAZ=q|T8x@5k%gAKL!fqC6H~tF$zLxiwLllT8Z?>OUh?SF-7R*s zZ#Nl%Z4U=0=9O4s7m<&a3m5v})zjG2zbG_0UkI;xx2n@n`dwac|@nqrb+ zrA&w#psGXk$wI2G;V#*EtBR}1scpTKf$dmm$sAYqt*+yloX8Nym1>IWaMs`wu64x& zdO0v4nQ(s!nqe99&cC+Q=%tpd?60@Q;m?_t$a#!Rh|@Eh59BNV>0d6f`1D^hHcDB> zBi^1eqS;x=R@T|n1KZ&so^S)O!e{-Qg_vK8g)Cx;tHB_BQVHqB>Wf_0*R_70Ne-;G zMz%>!x&BW|hzI9;>z1Xeh^8z3&brO^4XPc7rtAJ&dz>y#9)ZK8B~0FVg6!}^9;9;D zbq|;(DaOe-kj}LNi>*sbCQ=jrSVGY0r$?0)u zZTA)!*rD@jq-gkDNm$fblY%2}<2srmJg-`z4{CTvgH%J6V!6E}U2QWmIqTsDdhr)X(7zI!9K)_j(^O4SrS8=s22M&(U!B8>QAi9jGHn45 zK(a-st35u)t)5`R7^78S)y|ClogGQzU(OFm1C4sSv3kZ zvXPP{(2I@YykOm=M{>rb&R(ho2~uy_fCyznr>nY5;)q>mP$z$JR`+$ks->v9F6+C6 zrRy$_#n{KiN+;SvvkY{EU{Z_$z{|u0XYM>?&DzT6=6-yT)I(425ycn2Fy?oQsH`cA|gsJtUM!t)-LCWqwMDd?P%! zu_BqJbV+rTC9stjtIu;ZPj~1+t;v@v4r9uZwJ zlXQPJk{q%!@~LnTiB*}yW51sA0QmVZelkF_O_M2D)pI85G`(oKdc+p7LqW#%sIWoU zsyt@EhhAA4JRsPlT}L6L-E34gU?S&yE%S!|{);MA+;qnZ`) zhENT~hF5DH^0N!b3iwhmQFuTdbQV+3*pg{ur3F3Rr#C*;GFWUVSQ*7J#5R7+_81z7 zUsIj|5#AvejuT0GX1d;olbMroF@^~{h4w<`VH)LcjJ7eg+_B(vKru4597}O}f3_t%%8pLeci{IZM6CLAbEdKmTNEPI2plmki!_MLbD;_m zBg7OLkNpl)byx*;MC!Nz066?%*AEP{Wun9b)Cf_hm;$&m#->bgwx=H;MAWV)k1nxd zN~JyzqeE`|Pen7)fpqFA5x}!3wy(iIZpPW?|gbbs}WGr}i}Cdq%NXq_=OO}BTl+R5h1zK%Uvij9RaEz-|! z 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

eE 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≀&$0y5c^gs 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 literal 0 HcmV?d00001 diff --git a/tests/Tests.JWT.pas b/tests/Tests.JWT.pas index 84463163..973d1538 100644 --- a/tests/Tests.JWT.pas +++ b/tests/Tests.JWT.pas @@ -16,7 +16,7 @@ TMARSJWT = class(TObject) procedure Duration(const ASeconds: Int64); function GetTokenForVerifyOne: string; virtual; public - const DUMMY_SECRET = 'dummy_secret'; + const DUMMY_SECRET = '12345678901234567890123456789012'; [Test] procedure BuildOne; [Test] procedure VerifyOne; @@ -142,9 +142,10 @@ procedure TMARSJWT.Duration5secs; function TMARSJWT.GetTokenForVerifyOne: string; begin // beware: will expire one million days after Nov 15th, 2017 that is somewhere around Thu, 13 Oct 4755 :-D - Result := 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.' - + 'eyJkdXJhdGlvbiI6MTAwMDAwMCwiUm9sZXMiOiJzdGFuZGFyZCIsImlhdCI6MTUxMDczOTg0OCwiZXhwIjo4NzkxMDczNjI0OCwiQ2xhaW0yIjoxMjMsIlVzZXJOYW1lIjoiQW5kcmVhMSIsIkxBTkdVQUdFX0lEIjoxLCJpc3MiOiJNQVJTLUN1cmlvc2l0eSIsIkNsYWltMSI6IlByaW1vIn0.' - + '2DAhF5DWfTCPK13EYSkdlT2LRUA9kmHJcO9v-Gs0x6E'; + Result := + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9' + +'.eyJkdXJhdGlvbiI6MTAwMDAwMCwiUm9sZXMiOiJzdGFuZGFyZCIsImlhdCI6MTUxMDczOTg0OCwiZXhwIjo4NzkxMDczNjI0OCwiQ2xhaW0yIjoxMjMsIlVzZXJOYW1lIjoiQW5kcmVhMSIsIkxBTkdVQUdFX0lEIjoxLCJpc3MiOiJNQVJTLUN1cmlvc2l0eSIsIkNsYWltMSI6IlByaW1vIn0' + +'.HpiUlC0d-a-oA4rZRFOpQsHxML55B0vXL5BbtEQNnLI'; end; procedure TMARSJWT.VerifyOne;