Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
fpc-src / usr / share / fpcsrc / 3.2.0 / packages / fcl-web / src / restbridge / sqldbrestini.pp
Size: Mime:
{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2019 by the Free Pascal development team

    SQLDB REST Dispatcher .ini file load/save support.

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
unit sqldbrestini;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, sqldbrestio, sqldbrestauth, sqldbrestbridge, sqldbrestschema, inifiles;

Type
  TConnectionIniOption = (scoClearOnRead,      // Clear values first
                          scoSkipPassword,     // Do not save/load password
                          scoSkipMaskPassword, // do not mask the password
                          scoUserNameAsMask,   // use the username as mask for password
                          scoSkipParams        // Do not read/write params.
                         );
  TConnectionIniOptions = Set of TConnectionIniOption;

  TSQLDBRestConnectionHelper = class helper for TSQLDBRestConnection
  Private
    Procedure ClearValues;
  Public
    Procedure LoadFromIni(Const aIni: TCustomIniFile; aOptions : TConnectionIniOptions = []); overload;
    Procedure LoadFromIni(Const aIni: TCustomIniFile; ASection : String; aOptions : TConnectionIniOptions); overload;
    Procedure LoadFromIniFile(Const aFileName : String; aOptions : TConnectionIniOptions = []); overload;
    Procedure LoadFromIniFile(Const aFileName : String; Const ASection : String; aOptions : TConnectionIniOptions); overload;
    Procedure SaveToIniFile(Const aFileName : String; aOptions : TConnectionIniOptions = []);overload;
    Procedure SaveToIniFile(Const aFileName : String; Const ASection : String; aOptions : TConnectionIniOptions = []);overload;
    Procedure SaveToIni(Const aIni: TCustomIniFile; aOptions : TConnectionIniOptions = []); overload;
    Procedure SaveToIni(Const aIni: TCustomIniFile; ASection : String; aOptions : TConnectionIniOptions); overload;
  end;

  TDispatcherIniOption = (dioSkipReadConnections,   // Do not Read connection definitions
                          dioSkipExposeConnections, // Do not Expose connections defined in .ini file
                          dioSkipReadSchemas,       // Do not Read schema definitions
                          dioDisableSchemas,        // Do not enable schemas
                          dioSkipWriteConnections,  // Do not write connection definitions
                          dioSkipWriteSchemas,      // Do not Read schema definitions
                          dioSkipBasicAuth,         // Do not read/write basic auth data.
                          dioSkipStringConfig       // Do not read strings config
                          );
  TDispatcherIniOptions = set of TDispatcherIniOption;

  { TSQLDBRestDispatcherHelper }

  TSQLDBRestDispatcherHelper = class helper for TSQLDBRestDispatcher
  private
  Public
    procedure ReadSchemas(const aIni: TCustomIniFile; ASection: String; aOptions: TDispatcherIniOptions);
    procedure ReadConnections(const aIni: TCustomIniFile; ASection: String);
    procedure WriteConnections(const aIni: TCustomIniFile; ASection: String; aOptions : TConnectionIniOptions);
    procedure WriteSchemas(const aIni: TCustomIniFile; ASection: String; SchemaFileDir : String);
    Procedure LoadFromIni(Const aIni: TCustomIniFile; aOptions : TDispatcherIniOptions = []); overload;
    Procedure LoadFromIni(Const aIni: TCustomIniFile; ASection : String; aOptions : TDispatcherIniOptions); overload;
    Procedure LoadFromFile(Const aFileName : String; aOptions : TDispatcherIniOptions = []); overload;
    Procedure LoadFromFile(Const aFileName : String; Const ASection : String; aOptions : TDispatcherIniOptions); overload;
    Procedure SaveToFile(Const aFileName : String; aOptions : TDispatcherIniOptions = []);overload;
    Procedure SaveToFile(Const aFileName : String; Const ASection : String; aOptions : TDispatcherIniOptions = []);overload;
    Procedure SaveToIni(Const aIni: TCustomIniFile; aOptions : TDispatcherIniOptions = []); overload;
    Procedure SaveToIni(Const aIni: TCustomIniFile; ASection : String; aOptions : TDispatcherIniOptions); overload;
  end;

  { TRestStringsConfigHelper }

  TRestStringsConfigHelper = class helper for TRestStringsConfig
  Public
    Procedure LoadFromIni(Const aIni: TCustomIniFile); overload;
    Procedure LoadFromIni(Const aIni: TCustomIniFile; ASection : String); overload;
    Procedure LoadFromFile(Const aFileName : String); overload;
    Procedure LoadFromFile(Const aFileName : String; Const ASection : String); overload;
    Procedure SaveToFile(Const aFileName : String);overload;
    Procedure SaveToFile(Const aFileName : String; Const ASection : String);overload;
    Procedure SaveToIni(Const aIni: TCustomIniFile); overload;
    Procedure SaveToIni(Const aIni: TCustomIniFile; ASection : String); overload;
  end;


Function StrToOutputOptions(S : String) : TRestOutputOptions;
Function StrToDispatcherOptions(S : String) : TRestDispatcherOptions;
Function StrToConnectionIniOptions(S : String) : TConnectionIniOptions;
Function OutputOptionsToStr(Options : TRestOutputOptions): String;
Function DispatcherOptionsToStr(Options: TRestDispatcherOptions) : String;
Function ConnectionIniOptionsToStr(Options: TConnectionIniOptions): String;

Var
  TrivialEncryptKey : String = 'SQLDB';
  DefaultConnectionSection : String = 'Connection';
  DefaultDispatcherSection : String = 'Dispatcher';
  DefaultStringsConfigSection : String = 'Dispatcher_strings';

implementation

uses typinfo,strutils, sqldbrestauthini;

Const
  KeyHost = 'Host';
  KeyDatabaseName = 'DatabaseName';
  KeyUserName = 'UserName';
  KeyPassword = 'Password';
  KeyPort = 'Port';
  keyParams = 'Params';
  KeyCharset = 'Charset';
  KeyRole = 'Role';
  KeyType = 'Type';
  KeyConnections = 'Connections';
  KeySchemas = 'Schemas';
  keyDispatcherOptions = 'DispatcherOptions';
  keyOutputOptions = 'OutputOptions';
  KeyBasePath = 'BasePath';
  KeyDefaultConnection = 'DefaultConnection';
  KeyEnforceLimit = 'EnforceLimit';
  KeyCORSAllowedOrigins = 'CORSAllowedOrigins';
  KeyLoadOptions = 'LoadOptions';
  KeyMinFieldOptions = 'MinFieldOptions';
  KeyFileName = 'File';
  KeyEnabled = 'Enabled';
  KeyBasicAuth = 'BasicAuth';

Function StrToOutputOptions(S : String) : TRestOutputOptions;

var
  i : integer;

begin
  I:=StringToSet(PTypeInfo(TypeInfo(TRestOutputOptions)),S);
  Result:=TRestOutputOptions(I);
end;

Function StrToDispatcherOptions(S : String) : TRestDispatcherOptions;

var
  i : integer;

begin
  I:=StringToSet(PTypeInfo(TypeInfo(TRestDispatcherOptions)),S);
  Result:=TRestDispatcherOptions(I);
end;

Function StrToConnectionIniOptions(S : String) : TConnectionIniOptions;

var
  i : integer;

begin
  I:=StringToSet(PTypeInfo(TypeInfo(TConnectionIniOptions)),S);
  Result:=TConnectionIniOptions(I);
end;

Function StrToRestFieldOptions(S : String) : TRestFieldOptions;

var
  i : integer;

begin
  I:=StringToSet(PTypeInfo(TypeInfo(TRestFieldOptions)),S);
  Result:=TRestFieldOptions(I);
end;

Function OutputOptionsToStr(Options  : TRestOutputOptions): String;

begin
  Result:=SetToString(PTypeInfo(TypeInfo(TRestOutputOptions)),Integer(Options),False);
end;

Function DispatcherOptionsToStr(Options : TRestDispatcherOptions) : String;

begin
  Result:=SetToString(PTypeInfo(TypeInfo(TRestDispatcherOptions)),Integer(Options),false);
end;

Function ConnectionIniOptionsToStr(Options : TConnectionIniOptions): String;

begin
  Result:=SetToString(PTypeInfo(TypeInfo(TConnectionIniOptions)),Integer(Options),false);
end;

{ TRestStringsConfigHelper }

procedure TRestStringsConfigHelper.LoadFromIni(const aIni: TCustomIniFile);
begin
  LoadFromIni(aIni,DefaultStringsConfigSection);
end;

procedure TRestStringsConfigHelper.LoadFromIni(const aIni: TCustomIniFile; ASection: String);

Var
  T : TRestStringProperty;
  N : String;
  S : UTF8String;

begin
  For T in TRestStringProperty do
    begin
    Str(T,N);
    Delete(N,1,2);
    S:=aIni.ReadString(aSection, N, GetRestString(T));
    SetRestString(T,S);
    end;
end;

procedure TRestStringsConfigHelper.LoadFromFile(const aFileName: String);
begin
  LoadFromFile(aFileName,DefaultStringsConfigSection);
end;

procedure TRestStringsConfigHelper.LoadFromFile(const aFileName: String; const ASection: String);
Var
  Ini : TCustomIniFile;

begin
  Ini:=TMeminiFile.Create(aFileName);
  try
    LoadFromIni(Ini,aSection);
  finally
    Ini.Free;
  end;
end;

procedure TRestStringsConfigHelper.SaveToFile(const aFileName: String);
begin
  SaveToFile(aFileName,DefaultStringsConfigSection);
end;

procedure TRestStringsConfigHelper.SaveToFile(const aFileName: String; const ASection: String);
Var
  Ini : TCustomIniFile;

begin
  Ini:=TMeminiFile.Create(aFileName);
  try
    SaveToIni(Ini,aSection);
    Ini.UpdateFile;
  finally
    Ini.Free;
  end;
end;

procedure TRestStringsConfigHelper.SaveToIni(const aIni: TCustomIniFile);
begin
  SaveToini(aIni,DefaultStringsConfigSection);
end;

procedure TRestStringsConfigHelper.SaveToIni(const aIni: TCustomIniFile; ASection: String);
Var
  T : TRestStringProperty;
  N : String;

begin
  For T in TRestStringProperty do
    begin
    Str(T,N);
    Delete(N,1,2);
    aIni.WriteString(aSection, N, GetRestString(T));
    end;
end;



{ TSQLDBRestDispatcherHelper }

procedure TSQLDBRestDispatcherHelper.LoadFromIni(const aIni: TCustomIniFile; aOptions: TDispatcherIniOptions);
begin
  LoadFromIni(aIni,DefaultDispatcherSection,aOptions);
end;

procedure TSQLDBRestDispatcherHelper.ReadConnections(const aIni: TCustomIniFile; ASection: String);

Var
  S,L : String;
  I : Integer;
  C : TSQLDBRestConnection;
  CIO : TConnectionIniOptions;
begin
  // Read connections
  L:=aIni.ReadString(aSection,KeyConnections,'');
  For I:=1 to WordCount(L,[',']) do
    begin
    S:=ExtractWord(I,L,[',']);
    C:=Connections.AddConnection('','','','','');
    C.Name:=S;
    CIO:=StrToConnectionIniOptions(aIni.ReadString(S,KeyLoadOptions,''));
    C.LoadFromIni(aIni,S,CIO);
    end;
end;

procedure TSQLDBRestDispatcherHelper.WriteConnections(const aIni: TCustomIniFile; ASection: String; aOptions: TConnectionIniOptions);

Var
  S,L : String;
  I : Integer;

begin
  L:='';
  for I:=0 to Connections.Count-1 do
    begin
    if (L<>'') then
      L:=L+',';
    L:=L+Connections[i].Name;
    end;
  aIni.WriteString(aSection,KeyConnections,L);
  for I:=0 to Connections.Count-1 do
    begin
    S:=Connections[i].Name;
    L:=ConnectionIniOptionsToStr(aOptions);
    Connections[i].SaveToIni(aIni,S,aOptions);
    aIni.WriteString(S,KeyLoadOptions,L);
    end;
end;

procedure TSQLDBRestDispatcherHelper.WriteSchemas(const aIni: TCustomIniFile; ASection: String; SchemaFileDir : String);

Var
  S,L,FN : String;
  I : Integer;
  Sch : TSQLDBRestSchema;


begin
  // Read Schemas
  L:='';
  for I:=0 to Schemas.Count-1 do
    if Assigned(Schemas[i].Schema) then
      begin
      if (L<>'') then
        L:=L+',';
      L:=L+Schemas[i].Schema.Name;
      end;
  aIni.WriteString(aSection,KeySchemas,L);
  for I:=0 to Schemas.Count-1 do
    if Assigned(Schemas[i].Schema) then
      begin
      S:=Schemas[i].Schema.Name;
      Sch:=Schemas[i].Schema;
      if (SchemaFileDir<>'') then
        FN:=IncludeTrailingPathDelimiter(SchemaFileDir)+S+'.json'
      else
        FN:='';
      aIni.WriteString(S,KeyFileName,FN);
      aIni.WriteBool(S,KeyEnabled,Schemas[i].Enabled);
      if (FN<>'') then
        Sch.SaveToFile(FN);
    end;
end;

procedure TSQLDBRestDispatcherHelper.ReadSchemas(const aIni: TCustomIniFile; ASection: String; aOptions: TDispatcherIniOptions);

Var
  S,L,FN : String;
  I : Integer;
  Sch : TSQLDBRestSchema;
  SRef : TSQLDBRestSchemaRef;


begin
  // Read Schemas
  L:=aIni.ReadString(aSection,KeySchemas,'');
  For I:=1 to WordCount(L,[',']) do
    begin
    S:=ExtractWord(I,L,[',']);
    Sch:=TSQLDBRestSchema.Create(Self);
    Sch.Name:=S;
    SRef:=Schemas.AddSchema(Sch);
    SRef.Enabled:=aIni.ReadBool(S,KeyEnabled,True);
    if (dioDisableSchemas in aOptions) then
      SRef.Enabled:=False;
    FN:=aIni.ReadString(S,KeyFileName,'');
    if (FN<>'') then
      Sch.LoadFromFile(FN);
    end;
end;

procedure TSQLDBRestDispatcherHelper.LoadFromIni(const aIni: TCustomIniFile; ASection: String; aOptions: TDispatcherIniOptions);

Var
  I : Integer;
  FO : TRestFieldOptions;
  BAN : String;
  BA : TRestBasicAuthenticator;
  BAO : TBasicAuthIniOptions;

begin
  DispatchOptions:=StrToDispatcherOptions(aIni.ReadString(aSection,keyDispatcherOptions,''));
  OutputOptions:=StrToOutputOptions(aIni.ReadString(aSection,keyOutputOptions,''));
  BasePath:=aIni.ReadString(aSection,KeyBasePath,'');
  DefaultConnection:=aIni.ReadString(aSection,KeyDefaultConnection,'');
  EnforceLimit:=aIni.ReadInteger(aSection,KeyEnforceLimit,0);
  CORSAllowedOrigins:=aIni.ReadString(aSection,KeyCORSAllowedOrigins,'');
  if Not (dioSkipReadConnections in aOptions) then
    ReadConnections(aIni,aSection);
  if Not (dioSkipReadSchemas in aOptions) then
    ReadSchemas(aIni,aSection,aOptions);
  // Expose connections
  if not (dioSkipExposeConnections in aOptions) then
    for I:=0 to Connections.Count-1 do
      if Connections[i].Enabled then
        begin
        FO:=StrToRestFieldOptions(aIni.ReadString(Connections[i].Name,KeyMinFieldOptions,''));
        ExposeConnection(Connections[i],Nil,FO);
        end;
  if not (dioSkipBasicAuth in aOptions) then
    begin
    BAN:=aIni.ReadString(aSection,KeyBasicAuth,'');
    if BAN<>'' then
      begin
      BAO:=StrToBasicAuthIniOptions(aIni.ReadString(BAN,keyLoadOptions,''));
      BA:=TRestBasicAuthenticator.Create(Self);
      BA.Name:=BAN;
      BA.LoadFromIni(aIni,BAN,BAO);
      Self.Authenticator:=BA;
      end;
    end;
  if not (dioSkipStringConfig in aOptions) then
    Strings.LoadFromIni(aIni,aSection+'_strings');
end;

procedure TSQLDBRestDispatcherHelper.LoadFromFile(const aFileName: String; aOptions: TDispatcherIniOptions);
begin
  Loadfromfile(aFileName,DefaultDispatcherSection,aOptions);
end;

procedure TSQLDBRestDispatcherHelper.LoadFromFile(const aFileName: String; const ASection: String; aOptions: TDispatcherIniOptions);

Var
  Ini : TCustomIniFile;

begin
  Ini:=TMeminiFile.Create(aFileName);
  try
    LoadFromIni(Ini,aSection,aOptions);
  finally
    Ini.Free;
  end;
end;

procedure TSQLDBRestDispatcherHelper.SaveToFile(const aFileName: String; aOptions: TDispatcherIniOptions);
begin
  SaveTofile(aFileName,DefaultDispatcherSection,aOptions);
end;

procedure TSQLDBRestDispatcherHelper.SaveToFile(const aFileName: String; const ASection: String; aOptions: TDispatcherIniOptions);
Var
  Ini : TCustomIniFile;

begin
  Ini:=TMeminiFile.Create(aFileName);
  try
    SaveToIni(Ini,aSection,aOptions);
    Ini.UpdateFile;
  finally
    Ini.Free;
  end;
end;

procedure TSQLDBRestDispatcherHelper.SaveToIni(const aIni: TCustomIniFile; aOptions: TDispatcherIniOptions);
begin
  SaveToIni(aIni,DefaultDispatcherSection,aOptions);
end;

procedure TSQLDBRestDispatcherHelper.SaveToIni(const aIni: TCustomIniFile; ASection: String; aOptions: TDispatcherIniOptions);

Var
  BAN : String;

begin
  aIni.WriteString(aSection,keyDispatcherOptions,DispatcherOptionsToStr(DispatchOptions));
  aIni.WriteString(aSection,keyOutputOptions,OutputOptionsToStr(OutputOptions));
  aIni.WriteString(aSection,KeyBasePath,BasePath);
  aIni.WriteString(aSection,KeyDefaultConnection,DefaultConnection);
  aIni.WriteInteger(aSection,KeyEnforceLimit,EnforceLimit);
  aIni.WriteString(aSection,KeyCORSAllowedOrigins,CORSAllowedOrigins);
  if Not (dioSkipWriteConnections in aOptions) then
    WriteConnections(aIni,aSection,[]);
  if Not (dioSkipWriteSchemas in aOptions) then
    WriteSchemas(aIni,aSection,ExtractFilePath(ExpandFileName(aIni.FileName)));
  if not (dioSkipBasicAuth in aOptions) then
    if Assigned(Authenticator) and (Authenticator is TRestBasicAuthenticator) then
      begin
      BAN:=Authenticator.Name;
      if BAN='' then
        BAN:=Self.Name+'_basicauth';
      TRestBasicAuthenticator(Authenticator).SaveToIni(aIni,BAN,[]);
      aIni.WriteString(aSection,KeyBasicAuth,BAN);
      end;
  if not (dioSkipStringConfig in aOptions) then
    Strings.SaveToIni(aIni,aSection+'_strings');
end;

{ TSQLDBRestConnectionHelper }

procedure TSQLDBRestConnectionHelper.ClearValues;
begin
  HostName:='';
  DatabaseName:='';
  UserName:='';
  Password:='';
  CharSet:='';
  Params.Clear;
  Port:=0;
end;



Const
  ForbiddenParamKeys : Array[1..8] of unicodestring
                     = (keyHost,KeyDatabaseName,KeyUserName,KeyPassword,KeyPort,keyParams,keyCharSet,keyRole);
  ParamSeps = [',',';',' '];

procedure TSQLDBRestConnectionHelper.LoadFromIni(const aIni: TCustomIniFile; ASection: String; aOptions: TConnectionIniOptions);

Var
  M,N,P : String;
  I : integer;

begin
  With aIni do
    begin
    if (scoClearOnRead in aOptions) then
       ClearValues;
    ConnectionType:=ReadString(ASection,KeyType,ConnectionType);
    HostName:=ReadString(ASection,KeyHost,HostName);
    DatabaseName:=ReadString(ASection,KeyDatabaseName,DatabaseName);
    UserName:=ReadString(ASection,KeyUserName,UserName);
    CharSet:=ReadString(ASection,KeyCharSet,CharSet);
    Role:=ReadString(ASection,KeyRole,Role);
    Port:=ReadInteger(ASection,KeyPort,Port);
    Enabled:=ReadBool(ASection,KeyEnabled,True);
    // optional parts
    if not (scoSkipPassword in aOptions) then
      begin
      if scoSkipMaskPassword in aOptions then
        P:=ReadString(ASection,KeyPassword,Password)
      else
        begin
        P:=ReadString(ASection,KeyPassword,'');
        if (P<>'') then
          begin
          if scoUserNameAsMask in aOptions then
            M:=UserName
          else
            M:=TrivialEncryptKey;
          P:=XorDecode(M,P);
          end;
        end;
      Password:=P;
      end;
    if not (scoSkipParams in aOptions) then
      begin
      M:=ReadString(ASection,keyParams,'');
      For I:=1 to WordCount(M,ParamSeps) do
        begin
        N:=ExtractWord(I,M,ParamSeps);
        if IndexStr(Utf8Decode(N),ForbiddenParamKeys)=-1 then
          begin
          P:=ReadString(ASection,N,'');
          Params.Values[N]:=P;
          end;
        end;
      end;
    end;
end;

procedure TSQLDBRestConnectionHelper.LoadFromIni(const aIni: TCustomIniFile; aOptions: TConnectionIniOptions);
begin
  LoadFromIni(aIni,DefaultConnectionSection,aOptions);
end;

procedure TSQLDBRestConnectionHelper.LoadFromIniFile(const aFileName: String; aOptions: TConnectionIniOptions);


begin
  LoadfromInifile(aFileName,DefaultConnectionSection,aOptions);
end;

procedure TSQLDBRestConnectionHelper.LoadFromIniFile(const aFileName: String; const ASection: String; aOptions: TConnectionIniOptions);

Var
  Ini : TCustomIniFile;

begin
  Ini:=TMeminiFile.Create(aFileName);
  try
    LoadFromIni(Ini,aSection,aOptions);
  finally
    Ini.Free;
  end;
end;

procedure TSQLDBRestConnectionHelper.SaveToIniFile(const aFileName: String; aOptions: TConnectionIniOptions);
begin
  SaveToIniFile(aFileName,DefaultConnectionSection,aOptions);
end;

procedure TSQLDBRestConnectionHelper.SaveToIniFile(const aFileName: String; const ASection: String; aOptions: TConnectionIniOptions);
Var
  Ini : TCustomIniFile;

begin
  Ini:=TMeminiFile.Create(aFileName);
  try
    SaveToini(Ini,aSection,aOptions);
    Ini.UpdateFile;
  finally
    Ini.Free;
  end;
end;

procedure TSQLDBRestConnectionHelper.SaveToIni(const aIni: TCustomIniFile; aOptions: TConnectionIniOptions);
begin
  SaveToIni(aIni,DefaultConnectionSection,aOptions);
end;

procedure TSQLDBRestConnectionHelper.SaveToIni(const aIni: TCustomIniFile; ASection: String; aOptions: TConnectionIniOptions);
Var
  M,N,P : String;
  I : integer;

begin
  With aIni do
    begin
    WriteString(ASection,KeyHost,HostName);
    WriteString(ASection,KeyDatabaseName,DatabaseName);
    WriteString(ASection,KeyUserName,UserName);
    WriteString(ASection,KeyCharSet,CharSet);
    WriteString(ASection,KeyType,ConnectionType);
    WriteString(ASection,KeyRole,Role);
    WriteInteger(ASection,KeyPort,Port);
    WriteBool(ASection,KeyEnabled,Enabled);
    if not (scoSkipPassword in aOptions) then
      begin
      P:=Password;
      if Not (scoSkipMaskPassword in aOptions) then
        begin
        if scoUserNameAsMask in aOptions then
          M:=UserName
        else
          M:=TrivialEncryptKey;
        P:=XorEncode(M,P);
        end;
      WriteString(ASection,KeyPassword,P);
      end;
    if not (scoSkipParams in aOptions) then
      begin
      M:='';
      for I:=0 to Params.Count-1 do
        begin
        Params.GetNameValue(I,N,P);
        if (N<>'') and (IndexStr(Utf8Decode(N),ForbiddenParamKeys)=-1) then
          begin
          WriteString(ASection,N,P);
          if (M<>'') then
            M:=M+',';
          M:=M+N;
          end;
        end;
      WriteString(ASection,KeyParams,M);
      end;
    end;
end;

end.