Browse Source

* TPath.Combine rework by Bart B. Fixes issue #40513

(cherry picked from commit 63237dd375b1dbbedd793877283b874c9adc2fa4)
Michaël Van Canneyt 1 year ago
parent
commit
34c9d60a9b
2 changed files with 130 additions and 4 deletions
  1. 55 4
      packages/vcl-compat/src/system.ioutils.pp
  2. 75 0
      packages/vcl-compat/tests/tciotuils.pas

+ 55 - 4
packages/vcl-compat/src/system.ioutils.pp

@@ -180,7 +180,10 @@ type
     class function DriveExists(const aPath: string): Boolean;
     class function DriveExists(const aPath: string): Boolean;
     class function MatchesPattern(const FileName, Pattern: string; const CaseSensitive: Boolean): Boolean;
     class function MatchesPattern(const FileName, Pattern: string; const CaseSensitive: Boolean): Boolean;
     class function ChangeExtension(const aPath, Extension: string): string;
     class function ChangeExtension(const aPath, Extension: string): string;
-    class function Combine(const Path1, Path2: string): string;
+    class function Combine(const Path1, Path2: string; const ValidateParams: Boolean = True): string;
+    class function Combine(const Path1, Path2, Path3: string; const ValidateParams: Boolean = True): string;
+    class function Combine(const Path1, Path2, Path3, Path4: string; const ValidateParams: Boolean = True): string;
+    class function Combine(const Paths: array of string; const ValidateParams: Boolean = True): string;
     class function GetDirectoryName(FileName: string): string;
     class function GetDirectoryName(FileName: string): string;
     class function GetExtension(const FileName: string): string;
     class function GetExtension(const FileName: string): string;
     class function GetFileName(const FileName: string): string;
     class function GetFileName(const FileName: string): string;
@@ -846,7 +849,7 @@ begin
   Result:=ChangeFileExt(aPath, Extension);
   Result:=ChangeFileExt(aPath, Extension);
 end;
 end;
 
 
-class function TPath.Combine(const Path1, Path2: string): string;
+class function TPath.Combine(const Path1, Path2: string; const ValidateParams : Boolean = True): string;
 begin
 begin
   if (Path1='') or (Path2='') then
   if (Path1='') or (Path2='') then
     begin
     begin
@@ -858,13 +861,61 @@ begin
   else
   else
     begin
     begin
     if not TPath.HasValidPathChars(Path1,False) then
     if not TPath.HasValidPathChars(Path1,False) then
-      Raise EArgumentException.CreateFmt('Path %s has invalid characters',[Path1]);
+      Raise EArgumentException.CreateFmt(SErrInvalidCharsInPath,[Path1]);
     if not TPath.HasValidPathChars(Path2,False) then
     if not TPath.HasValidPathChars(Path2,False) then
-      Raise EArgumentException.CreateFmt('Path %s has invalid characters',[Path2]);
+      Raise EArgumentException.CreateFmt(SErrInvalidCharsInPath,[Path2]);
     Result:=ConcatPaths([Path1, Path2]);
     Result:=ConcatPaths([Path1, Path2]);
     end;
     end;
 end;
 end;
 
 
+class function TPath.Combine(const Path1, Path2, Path3 : string; const ValidateParams : Boolean = True): string;
+
+begin
+  Result:=Combine([Path1,Path2,Path3],ValidateParams);
+end;
+
+class function TPath.Combine(const Path1, Path2, Path3,Path4 : string; const ValidateParams : Boolean = True): string;
+
+begin
+  Result:=Combine([Path1,Path2,Path3,Path4],ValidateParams);
+end;
+
+class function TPath.Combine(const Paths: array of string; const ValidateParams: Boolean = True): string;
+  function AppendPathDelim(const Path: string): string;
+  begin
+    if (Path = '') or (Path[Length(Path)] in AllowDirectorySeparators)
+    {$ifdef mswindows}
+      //don't add a PathDelim to e.g. 'C:'
+      or ((Length(Path) = 2) and (Path[2] = ':') and (UpCase(Path[1]) in ['A'..'Z']))
+    {$endif}
+    then
+      Result:=Path
+    else
+      Result:=Path + DirectorySeparator;
+  end;
+var
+  i: Integer;
+  Path: String;
+begin
+  if ValidateParams then
+    for i := Low(Paths) to High(Paths) do
+      if not TPath.HasValidPathChars(Paths[i], False) then
+        Raise EInOutArgumentException.CreateFmt(SErrInvalidCharsInPath,[Paths[i]],Path[i]);
+  Result := '';
+  for i := High(Paths) downto Low(Paths) do
+  begin
+    Path := Paths[i];
+    if (Path <> '') then
+    begin
+      if (Result <> '') then
+        Path := AppendPathDelim(Path);
+      Result := Path + Result;
+      if not TPath.IsRelativePath(Result) then
+        Exit;
+    end;
+  end;
+end;
+
 class function TPath.GetDirectoryName(FileName: string): string;
 class function TPath.GetDirectoryName(FileName: string): string;
 begin
 begin
   Result:=ExcludeTrailingPathDelimiter(ExtractFileDir(FileName));
   Result:=ExcludeTrailingPathDelimiter(ExtractFileDir(FileName));

+ 75 - 0
packages/vcl-compat/tests/tciotuils.pas

@@ -41,6 +41,7 @@ type
     Procedure TestMatchesPattern;
     Procedure TestMatchesPattern;
     Procedure TestChangeExtension;
     Procedure TestChangeExtension;
     Procedure TestCombine;
     Procedure TestCombine;
+    Procedure TestCombineMulti;
     Procedure TestGetDirectoryName;
     Procedure TestGetDirectoryName;
     Procedure TestGetExtension;
     Procedure TestGetExtension;
     Procedure TestGetFileName;
     Procedure TestGetFileName;
@@ -390,6 +391,80 @@ begin
   TestIt('/path/a.doc','/path/','a.doc');
   TestIt('/path/a.doc','/path/','a.doc');
 end;
 end;
 
 
+procedure TTestTPath.TestCombineMulti;
+
+  procedure DoTest(const Paths: array of String; Validate: Boolean; Expected: string; ExceptionExpected: Boolean=False);
+
+    function ArgsToStr: string;
+    var
+      i: Integer;
+    begin
+      Result := '';
+      for i := Low(Paths) to High(Paths) do
+        Result := Result+''''+Paths[i] + ''',';
+      if (Result <> '') then SetLength(Result, Length(Result)-1);
+      Result := '['+Result+']';
+    end;
+
+  var
+    Res,FailMsg: String;
+    P : Array of string;
+    I : Integer;
+
+  begin
+    FailMsg:='';
+    try
+      SetLength(P,Length(Paths));
+      for I:=0 to Length(Paths)-1 do
+        begin
+        P[i]:=Paths[i];
+        DoDirSeparators(P[i]);
+        end;
+      DoDirSeparators(Expected);
+      Res := TPath.Combine(P,Validate);
+      AssertEquals(ArgsToStr,Expected,Res)
+    except
+      on E: Exception do
+        if not ExceptionExpected then
+          FailMsg:=Format('%s : an unexpected exception %s occurred: %s',[ArgsToStr,E.ClassName,E.Message])
+    end;
+    if FailMsg<>'' then
+      Fail(FailMsg);
+  end;
+
+  var
+    S: String;
+
+begin
+  //EInOutError
+  DoTest([''],True,'');
+  DoTest(['',''],True,'');
+  DoTest(['','',''],True,'');
+  DoTest(['a','b','c'],True,'a\b\c');
+  DoTest(['a','b','\c'],True,'\c');
+  DoTest(['a','\b','c'],True,'\b\c');
+  DoTest(['\a','\b','c'],True,'\b\c');
+  DoTest(['\a','\b','\c'],True,'\c');
+  DoTest(['\a','b','\c:'],True,'\c:');
+  DoTest(['a','<>','\b','c','\d'],True,'',True);
+  {$IFDEF WINDOWS}
+  DoTest(['c:','a','b'],True,'c:a\b',False);
+  {$ENDIF}
+  DoTest(['\1','..\2','..\3','..4'],True,'\1\..\2\..\3\..4');
+  DoTest(['\1','','','4','','6',''],True,'\1\4\6');
+  DoTest(['','','','<>|'],True,'<>|',True);
+  DoTest([''],False,'');
+  DoTest(['',''],False,'');
+  DoTest(['','',''],False,'');
+  DoTest(['a','b','c'],False,'a\b\c');
+  DoTest(['a','b','\c'],False,'\c');
+  DoTest(['a','\b','c'],False,'\b\c');
+  DoTest(['\a','\b','c'],False,'\b\c');
+  DoTest(['\a','\b','\c'],False,'\c');
+  DoTest(['\a','b','\c:'],False,'\c:');
+  DoTest(['a','<>','\b','c','\d'],False,'\d',False);
+end;
+
 procedure TTestTPath.TestGetDirectoryName;
 procedure TTestTPath.TestGetDirectoryName;
 
 
   Procedure TestIt(aResult,aFile : String);
   Procedure TestIt(aResult,aFile : String);