Browse Source

Bug 2052: File masks relative to the root of an operation

https://winscp.net/tracker/2052

Source commit: 6c037285152e369af4f530849a09848f0b9be880
Martin Prikryl 3 years ago
parent
commit
c5a60526ed

+ 135 - 39
source/core/FileMasks.cpp

@@ -17,6 +17,7 @@ static UnicodeString AllFileMasksDelimiters = FileMasksDelimiters + IncludeExclu
 static UnicodeString DirectoryMaskDelimiters = L"/\\";
 static UnicodeString FileMasksDelimiterStr = UnicodeString(FileMasksDelimiters[1]) + L' ';
 UnicodeString AnyMask = L"*.*";
+static UnicodeString MaskSymbols = L"?*[";
 //---------------------------------------------------------------------------
 __fastcall EFileMasksException::EFileMasksException(
     UnicodeString Message, int AErrorStart, int AErrorLen) :
@@ -148,7 +149,25 @@ UnicodeString TFileMasks::TParams::ToString() const
 //---------------------------------------------------------------------------
 bool __fastcall TFileMasks::IsMask(const UnicodeString Mask)
 {
-  return (Mask.LastDelimiter(L"?*[") > 0);
+  return (Mask.LastDelimiter(MaskSymbols) > 0);
+}
+//---------------------------------------------------------------------------
+UnicodeString TFileMasks::EscapeMask(const UnicodeString & S)
+{
+  UnicodeString Result = S;
+  if (Result.LastDelimiter(MaskSymbols) > 0) // optimization
+  {
+    for (int Index = 1; Index < Result.Length(); Index++)
+    {
+      if (MaskSymbols.Pos(Result[Index]) > 0)
+      {
+        Result.Insert(L"[", Index);
+        Index += 2;
+        Result.Insert(L"]", Index);
+      }
+    }
+  }
+  return Result;
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TFileMasks::NormalizeMask(const UnicodeString & Mask, const UnicodeString & AnyMask)
@@ -266,6 +285,8 @@ void TFileMasks::DoCopy(const TFileMasks & Source)
   FForceDirectoryMasks = Source.FForceDirectoryMasks;
   FNoImplicitMatchWithDirExcludeMask = Source.FNoImplicitMatchWithDirExcludeMask;
   FAllDirsAreImplicitlyIncluded = Source.FAllDirsAreImplicitlyIncluded;
+  FLocalRoot = Source.FLocalRoot;
+  FRemoteRoot = Source.FRemoteRoot;
   SetStr(Source.Masks, false);
 }
 //---------------------------------------------------------------------------
@@ -274,6 +295,7 @@ void __fastcall TFileMasks::Init()
   FForceDirectoryMasks = -1;
   FNoImplicitMatchWithDirExcludeMask = false;
   FAllDirsAreImplicitlyIncluded = false;
+  FAnyRelative = false;
 
   DoInit(false);
 }
@@ -305,15 +327,17 @@ void __fastcall TFileMasks::Clear(TMasks & Masks)
   TMasks::iterator I = Masks.begin();
   while (I != Masks.end())
   {
-    ReleaseMaskMask((*I).FileNameMask);
-    ReleaseMaskMask((*I).DirectoryMask);
+    delete (*I).FileNameMask;
+    delete (*I).RemoteDirectoryMask;
+    delete (*I).LocalDirectoryMask;
     I++;
   }
   Masks.clear();
 }
 //---------------------------------------------------------------------------
-bool __fastcall TFileMasks::MatchesMasks(const UnicodeString FileName, bool Directory,
-  const UnicodeString Path, const TParams * Params, const TMasks & Masks, bool Recurse)
+bool __fastcall TFileMasks::MatchesMasks(
+  const UnicodeString & FileName, bool Local, bool Directory,
+  const UnicodeString & Path, const TParams * Params, const TMasks & Masks, bool Recurse)
 {
   bool Result = false;
 
@@ -321,9 +345,10 @@ bool __fastcall TFileMasks::MatchesMasks(const UnicodeString FileName, bool Dire
   while (!Result && (I != Masks.end()))
   {
     const TMask & Mask = *I;
+    Masks::TMask * DirectoryMask = Local ? Mask.LocalDirectoryMask : Mask.RemoteDirectoryMask;
     Result =
-      MatchesMaskMask(Mask.DirectoryMask, Path) &&
-      MatchesMaskMask(Mask.FileNameMask, FileName);
+      MatchesMaskMask(Mask.DirectoryMaskKind, DirectoryMask, Path) &&
+      MatchesMaskMask(Mask.FileNameMaskKind, Mask.FileNameMask, FileName);
 
     if (Result)
     {
@@ -412,21 +437,25 @@ bool __fastcall TFileMasks::MatchesMasks(const UnicodeString FileName, bool Dire
     // Currently it includes Size/Time only, what is not used for directories.
     // So it depends on future use. Possibly we should make a copy
     // and pass on only relevant fields.
-    Result = MatchesMasks(ParentFileName, true, ParentPath, Params, Masks, Recurse);
+    Result = MatchesMasks(ParentFileName, Local, true, ParentPath, Params, Masks, Recurse);
   }
 
   return Result;
 }
 //---------------------------------------------------------------------------
 bool TFileMasks::DoMatches(
-  const UnicodeString & FileName, bool Directory, const UnicodeString & Path, const TParams * Params,
+  const UnicodeString & FileName, bool Local, bool Directory, const UnicodeString & Path, const TParams * Params,
   bool RecurseInclude, bool & ImplicitMatch) const
 {
+  if (FileName == L"aaa2.txt")
+  {
+    UnicodeString X;
+  }
   bool ImplicitIncludeMatch = (FAllDirsAreImplicitlyIncluded && Directory) || FMasks[MASK_INDEX(Directory, true)].empty();
-  bool ExplicitIncludeMatch = MatchesMasks(FileName, Directory, Path, Params, FMasks[MASK_INDEX(Directory, true)], RecurseInclude);
+  bool ExplicitIncludeMatch = MatchesMasks(FileName, Local, Directory, Path, Params, FMasks[MASK_INDEX(Directory, true)], RecurseInclude);
   bool Result =
     (ImplicitIncludeMatch || ExplicitIncludeMatch) &&
-    !MatchesMasks(FileName, Directory, Path, Params, FMasks[MASK_INDEX(Directory, false)], false);
+    !MatchesMasks(FileName, Local, Directory, Path, Params, FMasks[MASK_INDEX(Directory, false)], false);
   ImplicitMatch =
     Result && ImplicitIncludeMatch && !ExplicitIncludeMatch &&
     ((Directory && FNoImplicitMatchWithDirExcludeMask) || FMasks[MASK_INDEX(Directory, false)].empty());
@@ -451,12 +480,12 @@ bool __fastcall TFileMasks::Matches(const UnicodeString FileName, bool Local,
     {
       Path = ToUnixPath(ExcludeTrailingBackslash(Path));
     }
-    Result = DoMatches(ExtractFileName(FileName), Directory, Path, Params,
+    Result = DoMatches(ExtractFileName(FileName), Local, Directory, Path, Params,
       RecurseInclude, ImplicitMatch);
   }
   else
   {
-    Result = DoMatches(UnixExtractFileName(FileName), Directory,
+    Result = DoMatches(UnixExtractFileName(FileName), Local, Directory,
       SimpleUnixExcludeTrailingBackslash(UnixExtractFilePath(FileName)), Params,
       RecurseInclude, ImplicitMatch);
   }
@@ -466,7 +495,7 @@ bool __fastcall TFileMasks::Matches(const UnicodeString FileName, bool Local,
 bool TFileMasks::MatchesFileName(const UnicodeString & FileName, bool Directory, const TParams * Params) const
 {
   bool ImplicitMatch;
-  return DoMatches(FileName, Directory, EmptyStr, Params, true, ImplicitMatch);
+  return DoMatches(FileName, false, Directory, EmptyStr, Params, true, ImplicitMatch);
 }
 //---------------------------------------------------------------------------
 bool __fastcall TFileMasks::operator ==(const TFileMasks & rhm) const
@@ -499,21 +528,26 @@ void __fastcall TFileMasks::ThrowError(int Start, int End)
     Start, End - Start + 1);
 }
 //---------------------------------------------------------------------------
-void __fastcall TFileMasks::CreateMaskMask(const UnicodeString & Mask, int Start, int End,
-  bool Ex, TMaskMask & MaskMask)
+Masks::TMask * TFileMasks::DoCreateMaskMask(const UnicodeString & Str)
+{
+  return new Masks::TMask(Str);
+}
+//---------------------------------------------------------------------------
+void __fastcall TFileMasks::CreateMaskMask(
+  const UnicodeString & Mask, int Start, int End, bool Ex, TMask::TKind & MaskKind, Masks::TMask *& MaskMask)
 {
   try
   {
-    DebugAssert(MaskMask.Mask == NULL);
+    DebugAssert(MaskMask == NULL);
     if (Ex && !IsEffectiveFileNameMask(Mask))
     {
-      MaskMask.Kind = TMaskMask::Any;
-      MaskMask.Mask = NULL;
+      MaskKind = TMask::TKind::Any;
+      MaskMask = NULL;
     }
     else
     {
-      MaskMask.Kind = (Ex && (Mask == L"*.")) ? TMaskMask::NoExt : TMaskMask::Regular;
-      MaskMask.Mask = new Masks::TMask(Mask);
+      MaskKind = (Ex && (Mask == L"*.")) ? TMask::TKind::NoExt : TMask::TKind::Regular;
+      MaskMask = DoCreateMaskMask(Mask);
     }
   }
   catch(...)
@@ -544,10 +578,11 @@ void __fastcall TFileMasks::CreateMask(
 
   Mask.MaskStr = MaskStr;
   Mask.UserStr = MaskStr;
-  Mask.FileNameMask.Kind = TMaskMask::Any;
-  Mask.FileNameMask.Mask = NULL;
-  Mask.DirectoryMask.Kind = TMaskMask::Any;
-  Mask.DirectoryMask.Mask = NULL;
+  Mask.FileNameMaskKind = TMask::TKind::Any;
+  Mask.FileNameMask = NULL;
+  Mask.DirectoryMaskKind = TMask::TKind::Any;
+  Mask.RemoteDirectoryMask = NULL;
+  Mask.LocalDirectoryMask = NULL;
   Mask.HighSizeMask = TMask::None;
   Mask.LowSizeMask = TMask::None;
   Mask.HighModificationMask = TMask::None;
@@ -649,18 +684,53 @@ void __fastcall TFileMasks::CreateMask(
       if (D > 0)
       {
         // make sure sole "/" (root dir) is preserved as is
+        UnicodeString DirectoryMaskStr = SimpleUnixExcludeTrailingBackslash(ToUnixPath(PartStr.SubString(1, D)));
+        UnicodeString RemoteDirectoryMaskStr = DirectoryMaskStr;
+        UnicodeString LocalDirectoryMaskStr = DirectoryMaskStr;
+
+        const UnicodeString RelativePrefix = L".";
+        const UnicodeString RelativePrefixWithSlash = RelativePrefix + L"/";
+        if (DirectoryMaskStr == RelativePrefix)
+        {
+          FAnyRelative = true;
+          if (!FRemoteRoot.IsEmpty())
+          {
+            RemoteDirectoryMaskStr = SimpleUnixExcludeTrailingBackslash(FRemoteRoot);
+          }
+          if (!FLocalRoot.IsEmpty())
+          {
+            LocalDirectoryMaskStr = SimpleUnixExcludeTrailingBackslash(FLocalRoot);
+          }
+        }
+        else if (StartsStr(RelativePrefixWithSlash, DirectoryMaskStr))
+        {
+          FAnyRelative = true;
+          DirectoryMaskStr.Delete(1, RelativePrefixWithSlash.Length());
+          if (!FRemoteRoot.IsEmpty())
+          {
+            RemoteDirectoryMaskStr = FRemoteRoot + DirectoryMaskStr;
+          }
+          if (!FLocalRoot.IsEmpty())
+          {
+            LocalDirectoryMaskStr = FLocalRoot + DirectoryMaskStr;
+          }
+        }
+
         CreateMaskMask(
-          SimpleUnixExcludeTrailingBackslash(ToUnixPath(PartStr.SubString(1, D))),
-          PartStart, PartStart + D - 1, false,
-          Mask.DirectoryMask);
+          RemoteDirectoryMaskStr, PartStart, PartStart + D - 1, false,
+          Mask.DirectoryMaskKind, Mask.RemoteDirectoryMask);
+        if (Mask.RemoteDirectoryMask != NULL)
+        {
+          Mask.LocalDirectoryMask = DoCreateMaskMask(LocalDirectoryMaskStr);
+        }
         CreateMaskMask(
           PartStr.SubString(D + 1, PartStr.Length() - D),
           PartStart + D, PartEnd, true,
-          Mask.FileNameMask);
+          Mask.FileNameMaskKind, Mask.FileNameMask);
       }
       else
       {
-        CreateMaskMask(PartStr, PartStart, PartEnd, true, Mask.FileNameMask);
+        CreateMaskMask(PartStr, PartStart, PartEnd, true, Mask.FileNameMaskKind, Mask.FileNameMask);
       }
     }
   }
@@ -684,11 +754,6 @@ TStrings * __fastcall TFileMasks::GetMasksStr(int Index) const
   return FMasksStr[Index];
 }
 //---------------------------------------------------------------------------
-void __fastcall TFileMasks::ReleaseMaskMask(TMaskMask & MaskMask)
-{
-  delete MaskMask.Mask;
-}
-//---------------------------------------------------------------------------
 void __fastcall TFileMasks::TrimEx(UnicodeString & Str, int & Start, int & End)
 {
   UnicodeString Buf = TrimLeft(Str);
@@ -697,20 +762,20 @@ void __fastcall TFileMasks::TrimEx(UnicodeString & Str, int & Start, int & End)
   End -= Buf.Length() - Str.Length();
 }
 //---------------------------------------------------------------------------
-bool __fastcall TFileMasks::MatchesMaskMask(const TMaskMask & MaskMask, const UnicodeString & Str)
+bool TFileMasks::MatchesMaskMask(TMask::TKind MaskKind, Masks::TMask * MaskMask, const UnicodeString & Str)
 {
   bool Result;
-  if (MaskMask.Kind == TMaskMask::Any)
+  if (MaskKind == TMask::TKind::Any)
   {
     Result = true;
   }
-  else if ((MaskMask.Kind == TMaskMask::NoExt) && (Str.Pos(L".") == 0))
+  else if ((MaskKind == TMask::TKind::NoExt) && (Str.Pos(L".") == 0))
   {
     Result = true;
   }
   else
   {
-    Result = MaskMask.Mask->Matches(Str);
+    Result = MaskMask->Matches(Str);
   }
   return Result;
 }
@@ -730,6 +795,7 @@ void __fastcall TFileMasks::SetMask(const UnicodeString & Mask)
 //---------------------------------------------------------------------------
 void __fastcall TFileMasks::SetStr(const UnicodeString Str, bool SingleMask)
 {
+  FAnyRelative = false;
   UnicodeString Backup = FStr;
   try
   {
@@ -785,6 +851,36 @@ void __fastcall TFileMasks::SetStr(const UnicodeString Str, bool SingleMask)
   }
 }
 //---------------------------------------------------------------------------
+void TFileMasks::SetRoots(const UnicodeString & LocalRoot, const UnicodeString & RemoteRoot)
+{
+  if (FAnyRelative) // optimization
+  {
+    FLocalRoot = EscapeMask(UnixIncludeTrailingBackslash(ToUnixPath(LocalRoot)));
+    FRemoteRoot = EscapeMask(UnixIncludeTrailingBackslash(RemoteRoot));
+    SetStr(FStr, false);
+  }
+}
+//---------------------------------------------------------------------------
+void TFileMasks::SetRoots(TStrings * LocalFileList, const UnicodeString & RemoteRoot)
+{
+  if (FAnyRelative) // optimization
+  {
+    UnicodeString LocalRoot;
+    ExtractCommonPath(LocalFileList, LocalRoot);
+    SetRoots(LocalRoot, RemoteRoot);
+  }
+}
+//---------------------------------------------------------------------------
+void TFileMasks::SetRoots(const UnicodeString & LocalRoot, TStrings * RemoteFileList)
+{
+  if (FAnyRelative) // optimization
+  {
+    UnicodeString RemoteRoot;
+    UnixExtractCommonPath(RemoteFileList, RemoteRoot);
+    SetRoots(LocalRoot, RemoteRoot);
+  }
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #define TEXT_TOKEN L'\255'
 //---------------------------------------------------------------------------

+ 23 - 15
source/core/FileMasks.h

@@ -29,6 +29,7 @@ public:
   };
 
   static bool __fastcall IsMask(const UnicodeString Mask);
+  static UnicodeString EscapeMask(const UnicodeString & S);
   static UnicodeString __fastcall NormalizeMask(const UnicodeString & Mask, const UnicodeString & AnyMask = L"");
   static UnicodeString __fastcall ComposeMaskStr(
     TStrings * IncludeFileMasksStr, TStrings * ExcludeFileMasksStr,
@@ -53,6 +54,10 @@ public:
   bool __fastcall Matches(const UnicodeString FileName, bool Local, bool Directory,
     const TParams * Params, bool RecurseInclude, bool & ImplicitMatch) const;
 
+  void SetRoots(const UnicodeString & LocalRoot, const UnicodeString & RemoteRoot);
+  void SetRoots(TStrings * LocalFileList, const UnicodeString & RemoteRoot);
+  void SetRoots(const UnicodeString & LocalRoot, TStrings * RemoteFileList);
+
   __property UnicodeString Masks = { read = FStr, write = SetMasks };
   __property bool NoImplicitMatchWithDirExcludeMask = { read = FNoImplicitMatchWithDirExcludeMask, write = FNoImplicitMatchWithDirExcludeMask };
   __property bool AllDirsAreImplicitlyIncluded = { read = FAllDirsAreImplicitlyIncluded, write = FAllDirsAreImplicitlyIncluded };
@@ -67,17 +72,19 @@ private:
   UnicodeString FStr;
   bool FNoImplicitMatchWithDirExcludeMask;
   bool FAllDirsAreImplicitlyIncluded;
-
-  struct TMaskMask
-  {
-    enum { Any, NoExt, Regular } Kind;
-    TMask * Mask;
-  };
+  bool FAnyRelative;
+  UnicodeString FLocalRoot;
+  UnicodeString FRemoteRoot;
 
   struct TMask
   {
-    TMaskMask FileNameMask;
-    TMaskMask DirectoryMask;
+    enum TKind { Any, NoExt, Regular };
+
+    TKind FileNameMaskKind;
+    Masks::TMask * FileNameMask;
+    TKind DirectoryMaskKind;
+    Masks::TMask * RemoteDirectoryMask;
+    Masks::TMask * LocalDirectoryMask;
 
     enum TMaskBoundary { None, Open, Close };
 
@@ -101,25 +108,26 @@ private:
 
   void __fastcall SetStr(const UnicodeString value, bool SingleMask);
   void __fastcall SetMasks(const UnicodeString value);
-  void __fastcall CreateMaskMask(const UnicodeString & Mask, int Start, int End,
-    bool Ex, TMaskMask & MaskMask);
+  void __fastcall CreateMaskMask(
+    const UnicodeString & Mask, int Start, int End, bool Ex, TMask::TKind & MaskKind, Masks::TMask *& MaskMask);
   void __fastcall CreateMask(const UnicodeString & MaskStr, int MaskStart,
     int MaskEnd, bool Include);
   TStrings * __fastcall GetMasksStr(int Index) const;
   static UnicodeString __fastcall MakeDirectoryMask(UnicodeString Str);
-  static inline void __fastcall ReleaseMaskMask(TMaskMask & MaskMask);
   inline void __fastcall Init();
   void __fastcall DoInit(bool Delete);
   void DoCopy(const TFileMasks & Source);
   void __fastcall Clear();
   static void __fastcall Clear(TMasks & Masks);
   static void __fastcall TrimEx(UnicodeString & Str, int & Start, int & End);
-  static bool __fastcall MatchesMasks(const UnicodeString FileName, bool Directory,
-    const UnicodeString Path, const TParams * Params, const TMasks & Masks, bool Recurse);
-  static inline bool __fastcall MatchesMaskMask(const TMaskMask & MaskMask, const UnicodeString & Str);
+  static bool __fastcall MatchesMasks(
+    const UnicodeString & FileName, bool Local, bool Directory,
+    const UnicodeString & Path, const TParams * Params, const TMasks & Masks, bool Recurse);
+  static inline bool MatchesMaskMask(TMask::TKind MaskKind, Masks::TMask * MaskMask, const UnicodeString & Str);
+  static Masks::TMask * DoCreateMaskMask(const UnicodeString & Str);
   void __fastcall ThrowError(int Start, int End);
   bool DoMatches(
-    const UnicodeString & FileName, bool Directory, const UnicodeString & Path, const TParams * Params,
+    const UnicodeString & FileName, bool Local, bool Directory, const UnicodeString & Path, const TParams * Params,
     bool RecurseInclude, bool & ImplicitMatch) const;
 };
 //---------------------------------------------------------------------------

+ 16 - 2
source/core/RemoteFiles.cpp

@@ -181,18 +181,32 @@ bool __fastcall ExtractCommonPath(TStrings * Files, UnicodeString & Path)
   return Result;
 }
 //---------------------------------------------------------------------------
+static UnicodeString GetFileListItemPath(TStrings * Files, int Index)
+{
+  UnicodeString Result;
+  if (Files->Objects[Index] != NULL)
+  {
+    Result = DebugNotNull(dynamic_cast<TRemoteFile *>(Files->Objects[Index]))->FullFileName;
+  }
+  else
+  {
+    Result = Files->Strings[Index];
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 bool __fastcall UnixExtractCommonPath(TStrings * Files, UnicodeString & Path)
 {
   DebugAssert(Files->Count > 0);
 
-  Path = UnixExtractFilePath(Files->Strings[0]);
+  Path = UnixExtractFilePath(GetFileListItemPath(Files, 0));
   bool Result = !Path.IsEmpty();
   if (Result)
   {
     for (int Index = 1; Index < Files->Count; Index++)
     {
       while (!Path.IsEmpty() &&
-        (Files->Strings[Index].SubString(1, Path.Length()) != Path))
+        (GetFileListItemPath(Files, Index).SubString(1, Path.Length()) != Path))
       {
         int PrevLen = Path.Length();
         Path = UnixExtractFilePath(UnixExcludeTrailingBackslash(Path));

+ 3 - 0
source/core/Script.cpp

@@ -1498,6 +1498,7 @@ void __fastcall TScript::GetProc(TScriptProcParams * Parameters)
     }
 
     CheckParams(Parameters);
+    CopyParam.IncludeFileMask.SetRoots(TargetDirectory, FileList);
 
     FTerminal->CopyToLocal(FileList, TargetDirectory, &CopyParam, Params, NULL);
   }
@@ -1569,6 +1570,7 @@ void __fastcall TScript::PutProc(TScriptProcParams * Parameters)
     }
 
     CheckParams(Parameters);
+    CopyParam.IncludeFileMask.SetRoots(FileList, TargetDirectory);
 
     FTerminal->CopyToRemote(FileList, TargetDirectory, &CopyParam, Params, NULL);
   }
@@ -1973,6 +1975,7 @@ void __fastcall TScript::SynchronizeProc(TScriptProcParams * Parameters)
     UnicodeString RemoteDirectory;
 
     SynchronizeDirectories(Parameters, LocalDirectory, RemoteDirectory, 2);
+    CopyParam.IncludeFileMask.SetRoots(LocalDirectory, RemoteDirectory);
 
     CheckDefaultSynchronizeParams();
     int SynchronizeParams = FSynchronizeParams | TTerminal::spNoConfirmation;

+ 11 - 2
source/forms/CustomScpExplorer.cpp

@@ -1262,7 +1262,7 @@ void __fastcall TCustomScpExplorerForm::ClearTransferSourceSelection(TTransferDi
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::AddQueueItem(
   TTerminalQueue * Queue, TTransferDirection Direction, TStrings * FileList,
-  const UnicodeString TargetDirectory, const TGUICopyParamType & CopyParam,
+  const UnicodeString TargetDirectory, TGUICopyParamType & CopyParam,
   int Params)
 {
   DebugAssert(Queue != NULL);
@@ -1285,11 +1285,13 @@ void __fastcall TCustomScpExplorerForm::AddQueueItem(
   TQueueItem * QueueItem;
   if (Direction == tdToRemote)
   {
+    CopyParam.IncludeFileMask.SetRoots(FileList, TargetDirectory);
     QueueItem = new TUploadQueueItem(Terminal, FileList, TargetDirectory,
       &CopyParam, Params, SingleFile, CopyParam.QueueParallel);
   }
   else
   {
+    CopyParam.IncludeFileMask.SetRoots(TargetDirectory, FileList);
     QueueItem = new TDownloadQueueItem(Terminal, FileList, TargetDirectory,
       &CopyParam, Params, SingleFile, CopyParam.QueueParallel);
   }
@@ -2741,6 +2743,9 @@ bool __fastcall TCustomScpExplorerForm::ExecuteCopyMoveFileOperation(
           PermanentFileList = FileList;
 
           Params |= FLAGMASK(Param.Temp, cpTemporary);
+
+          CopyParam.IncludeFileMask.SetRoots(FileList, Param.TargetDirectory);
+
           Terminal->CopyToRemote(FileList, Param.TargetDirectory, &CopyParam, Params, NULL);
           if (Operation == foMove)
           {
@@ -2763,6 +2768,8 @@ bool __fastcall TCustomScpExplorerForm::ExecuteCopyMoveFileOperation(
 
           try
           {
+            CopyParam.IncludeFileMask.SetRoots(Param.TargetDirectory, FileList);
+
             Terminal->CopyToLocal(FileList, Param.TargetDirectory, &CopyParam, Params, NULL);
           }
           __finally
@@ -6167,6 +6174,7 @@ int __fastcall TCustomScpExplorerForm::DoFullSynchronizeDirectories(
   if (Continue)
   {
     Configuration->Usage->Inc(L"Synchronizations");
+    CopyParam.IncludeFileMask.SetRoots(LocalDirectory, RemoteDirectory);
     UpdateCopyParamCounters(CopyParam);
 
     TSynchronizeOptions SynchronizeOptions;
@@ -8098,10 +8106,11 @@ void __fastcall TCustomScpExplorerForm::RemoteFileControlDDTargetDrop()
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::DDDownload(
-  TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params)
+  TStrings * FilesToCopy, const UnicodeString & TargetDir, TCopyParamType * CopyParam, int Params)
 {
   DebugAssert(!IsLocalBrowserMode());
   TAutoBatch AutoBatch(this);
+  CopyParam->IncludeFileMask.SetRoots(TargetDir, FilesToCopy);
   UpdateCopyParamCounters(*CopyParam);
   Terminal->CopyToLocal(FilesToCopy, TargetDir, CopyParam, Params, NULL);
   if (FLAGSET(Params, cpDelete) && (DropSourceControl == RemoteDriveView))

+ 4 - 3
source/forms/CustomScpExplorer.h

@@ -326,9 +326,10 @@ private:
   void __fastcall SetDockAllowDrag(bool value);
   void __fastcall QueueSplitterDblClick(TObject * Sender);
   void __fastcall QueueFileListSplitterDblClick(TObject * Sender);
-  void __fastcall AddQueueItem(TTerminalQueue * Queue, TTransferDirection Direction,
+  void __fastcall AddQueueItem(
+    TTerminalQueue * Queue, TTransferDirection Direction,
     TStrings * FileList, const UnicodeString TargetDirectory,
-    const TGUICopyParamType & CopyParam, int Params);
+    TGUICopyParamType & CopyParam, int Params);
   void __fastcall AddQueueItem(TTerminalQueue * Queue, TQueueItem * QueueItem, TManagedTerminal * Terminal);
   void __fastcall ClearTransferSourceSelection(TTransferDirection Direction);
   void ClearOperationSelection(TOperationSide Side);
@@ -540,7 +541,7 @@ protected:
   bool __fastcall RemoteFileControlFileOperation(TObject * Sender,
     TFileOperation Operation, bool NoConfirmation, void * Param);
   void __fastcall DDDownload(
-    TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params);
+    TStrings * FilesToCopy, const UnicodeString & TargetDir, TCopyParamType * CopyParam, int Params);
   bool __fastcall EnsureCommandSessionFallback(TFSCapability Capability);
   bool __fastcall CommandSessionFallback();
   void __fastcall FileTerminalRemoved(const UnicodeString FileName,

+ 1 - 0
source/windows/SynchronizeController.cpp

@@ -57,6 +57,7 @@ void __fastcall TSynchronizeController::StartStop(TObject * Sender,
       }
 
       FCopyParam = CopyParam;
+      FCopyParam.IncludeFileMask.SetRoots(Params.LocalDirectory, Params.RemoteDirectory);
       FSynchronizeParams = Params;
 
       DebugAssert(OnAbort);

+ 3 - 0
source/windows/WinMain.cpp

@@ -118,6 +118,7 @@ void __fastcall Upload(TTerminal * Terminal, TStrings * FileList, int UseDefault
   {
     // Setting parameter overrides only now, otherwise the dialog would present the parametes as non-default
     CopyParam.OnceDoneOperation = odoDisconnect;
+    CopyParam.IncludeFileMask.SetRoots(FileList, TargetDirectory);
 
     Terminal->CopyToRemote(FileList, TargetDirectory, &CopyParam, 0, NULL);
   }
@@ -180,6 +181,8 @@ void __fastcall Download(TTerminal * Terminal, const UnicodeString FileName, int
 
       std::unique_ptr<TStrings> FileList(new TStringList());
       FileList->AddObject(FileName, File);
+      CopyParam.IncludeFileMask.SetRoots(TargetDirectory, FileList.get());
+
       Terminal->CopyToLocal(FileList.get(), TargetDirectory, &CopyParam, 0, NULL);
     }