瀏覽代碼

Bug 1641: Support ACL for S3

https://winscp.net/tracker/1641
(cherry picked from commit 9b304c1b326f9b917754fdce256b4b2b93032e04)

# Conflicts:
#	source/forms/CustomScpExplorer.h

Source commit: bf3c1b88326df1dc340f6ca06f93953db7c82c27
Martin Prikryl 4 年之前
父節點
當前提交
d2f9693f1c

+ 1 - 1
libs/libs3/src/general.c

@@ -388,7 +388,7 @@ static S3Status convertAclXmlCallback(const char *elementPath,
                 strcpy(grant->grantee.amazonCustomerByEmail.emailAddress,
                        caData->emailAddress);
             }
-            else if (caData->userId[0] && caData->userDisplayName[0]) {
+            else if (caData->userId[0]) {
                 grant->granteeType = S3GranteeTypeCanonicalUser;
                 strcpy(grant->grantee.canonicalUser.id, caData->userId);
                 strcpy(grant->grantee.canonicalUser.displayName,

+ 1 - 0
source/core/FtpFileSystem.cpp

@@ -1908,6 +1908,7 @@ bool __fastcall TFTPFileSystem::IsCapable(int Capability) const
     case fcCheckingSpaceAvailable:
       return FBytesAvailableSupported || SupportsCommand(AvblCommand) || SupportsCommand(XQuotaCommand);
 
+    case fcAclChangingFiles:
     case fcModeChangingUpload:
     case fcLoadingAdditionalProperties:
     case fcShellAnyCommand:

+ 55 - 0
source/core/RemoteFiles.cpp

@@ -2046,11 +2046,47 @@ void __fastcall TRights::Assign(const TRights * Source)
   FUnknown = Source->FUnknown;
 }
 //---------------------------------------------------------------------------
+TRights::TRight TRights::CalculateRight(TRightGroup Group, TRightLevel Level)
+{
+  int Result;
+  if (Level == rlSpecial)
+  {
+    Result = rrUserIDExec + Group;
+  }
+  else
+  {
+    DebugAssert(rlRead == 0);
+    Result = rrUserRead + Level + (Group * 3);
+  }
+  return static_cast<TRight>(Result);
+}
+//---------------------------------------------------------------------------
 TRights::TFlag __fastcall TRights::RightToFlag(TRights::TRight Right)
 {
   return static_cast<TFlag>(1 << (rrLast - Right));
 }
 //---------------------------------------------------------------------------
+TRights::TFlag TRights::CalculateFlag(TRightGroup Group, TRightLevel Level)
+{
+  return RightToFlag(CalculateRight(Group, Level));
+}
+//---------------------------------------------------------------------------
+unsigned short TRights::CalculatePermissions(TRightGroup Group, TRightLevel Level, TRightLevel Level2, TRightLevel Level3)
+{
+  unsigned int Permissions = CalculateFlag(Group, Level);
+  if (Level2 != rlNone)
+  {
+    Permissions |= CalculateFlag(Group, Level2);
+  }
+  if (Level3 != rlNone)
+  {
+    Permissions |= CalculateFlag(Group, Level3);
+  }
+  unsigned short Result = static_cast<unsigned short>(Permissions);
+  DebugAssert((Permissions - Result) == 0);
+  return Result;
+}
+//---------------------------------------------------------------------------
 bool __fastcall TRights::operator ==(const TRights & rhr) const
 {
   if (AllowUndef || rhr.AllowUndef)
@@ -2234,6 +2270,15 @@ void __fastcall TRights::SetText(const UnicodeString & value)
   FUnknown = false;
 }
 //---------------------------------------------------------------------------
+void TRights::SetTextOverride(const UnicodeString & value)
+{
+  if (FText != value)
+  {
+    FText = value;
+    FUnknown = false;
+  }
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall TRights::GetText() const
 {
   if (!FText.IsEmpty())
@@ -2318,6 +2363,7 @@ void __fastcall TRights::SetOctal(UnicodeString value)
       ((AValue[2] - L'0') << 6) +
       ((AValue[3] - L'0') << 3) +
       ((AValue[4] - L'0') << 0));
+    FText = L"";
   }
   FUnknown = false;
 }
@@ -2527,6 +2573,7 @@ void __fastcall TRights::AddExecute()
         (RightUndef[static_cast<TRight>(rrUserWrite + (Group * 3))] == rsYes))
     {
       Right[static_cast<TRight>(rrUserExec + (Group * 3))] = true;
+      FText = L"";
     }
   }
   FUnknown = false;
@@ -2557,6 +2604,14 @@ __fastcall TRights::operator unsigned long() const
 {
   return Number;
 }
+//---------------------------------------------------------------------------
+TRights TRights::Combine(const TRights & Other) const
+{
+  TRights Result = (*this);
+  Result |= Other.NumberSet;
+  Result &= (unsigned short)~Other.NumberUnset;
+  return Result;
+}
 //=== TRemoteProperties -------------------------------------------------------
 __fastcall TRemoteProperties::TRemoteProperties()
 {

+ 19 - 1
source/core/RemoteFiles.h

@@ -323,6 +323,17 @@ public:
   static const wchar_t CombinedSymbols[];
   static const wchar_t ExtendedSymbols[];
   static const wchar_t ModeGroups[];
+  enum TRightLevel {
+    rlNone = -1,
+    rlRead, rlWrite, rlExec, rlSpecial,
+    rlFirst = rlRead, rlLastNormal = rlExec, rlLastWithSpecial = rlSpecial,
+    rlS3Read = rlRead, rlS3Write = rlWrite, rlS3ReadACP = rlExec, rlS3WriteACP = rlSpecial, rlLastAcl = rlLastWithSpecial,
+  };
+  enum TRightGroup {
+    rgUser, rgGroup, rgOther,
+    rgFirst = rgUser, rgLast = rgOther,
+    rgS3AllAwsUsers = rgGroup, rgS3AllUsers = rgOther,
+  };
   enum TRight {
     rrUserIDExec, rrGroupIDExec, rrStickyBit,
     rrUserRead, rrUserWrite, rrUserExec,
@@ -336,13 +347,18 @@ public:
     rfOtherRead = 00004, rfOtherWrite =  00002, rfOtherExec = 00001,
     rfRead =      00444, rfWrite =       00222, rfExec =      00111,
     rfNo =        00000, rfDefault =     00644, rfAll =       00777,
-    rfSpecials =  07000, rfAllSpecials = 07777 };
+    rfSpecials =  07000, rfAllSpecials = 07777,
+    rfS3Read = rfOtherRead, rfS3Write = rfOtherWrite, rfS3ReadACP = rfOtherExec, rfS3WriteACP = rfStickyBit,
+     };
   enum TUnsupportedFlag {
     rfDirectory  = 040000 };
   enum TState { rsNo, rsYes, rsUndef };
 
 public:
   static TFlag __fastcall RightToFlag(TRight Right);
+  static TRight CalculateRight(TRightGroup Group, TRightLevel Level);
+  static TFlag CalculateFlag(TRightGroup Group, TRightLevel Level);
+  static unsigned short CalculatePermissions(TRightGroup Group, TRightLevel Level, TRightLevel Level2 = rlNone, TRightLevel Level3 = rlNone);
 
   __fastcall TRights();
   __fastcall TRights(const TRights & Source);
@@ -350,6 +366,8 @@ public:
   void __fastcall Assign(const TRights * Source);
   void __fastcall AddExecute();
   void __fastcall AllUndef();
+  TRights Combine(const TRights & Other) const;
+  void SetTextOverride(const UnicodeString & value);
 
   bool __fastcall operator ==(const TRights & rhr) const;
   bool __fastcall operator ==(unsigned short rhr) const;

+ 304 - 6
source/core/S3FileSystem.cpp

@@ -763,6 +763,8 @@ bool __fastcall TS3FileSystem::IsCapable(int Capability) const
     case fcMoveToQueue:
     case fcSkipTransfer:
     case fcParallelTransfers:
+    case fcLoadingAdditionalProperties:
+    case fcAclChangingFiles:
       return true;
 
     case fcPreservingTimestampUpload:
@@ -780,7 +782,6 @@ bool __fastcall TS3FileSystem::IsCapable(int Capability) const
     case fcNativeTextMode:
     case fcNewerOnlyUpload:
     case fcTimestampChanging:
-    case fcLoadingAdditionalProperties:
     case fcIgnorePermErrors:
     case fcCalculatingChecksum:
     case fcSecondaryShell:
@@ -1332,17 +1333,314 @@ void __fastcall TS3FileSystem::CreateLink(const UnicodeString FileName,
   DebugFail();
 }
 //---------------------------------------------------------------------------
+struct TS3FileProperties
+{
+  char OwnerId[S3_MAX_GRANTEE_USER_ID_SIZE];
+  char OwnerDisplayName[S3_MAX_GRANTEE_DISPLAY_NAME_SIZE];
+  int AclGrantCount;
+  S3AclGrant AclGrants[S3_MAX_ACL_GRANT_COUNT];
+};
+//---------------------------------------------------------------------------
+static TRights::TRightLevel S3PermissionToRightLevel(S3Permission Permission)
+{
+  TRights::TRightLevel Result;
+  switch (Permission)
+  {
+    case S3PermissionRead: Result = TRights::rlS3Read; break;
+    case S3PermissionWrite: Result = TRights::rlS3Write; break;
+    case S3PermissionReadACP: Result = TRights::rlS3ReadACP; break;
+    case S3PermissionWriteACP: Result = TRights::rlS3WriteACP; break;
+    default: DebugFail(); Result = TRights::rlNone; break;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+bool TS3FileSystem::ParsePathForPropertiesRequests(
+  const UnicodeString & Path, const TRemoteFile * File, UnicodeString & BucketName, UnicodeString & Key)
+{
+  UnicodeString FileName = AbsolutePath(Path, false);
+
+  ParsePath(FileName, BucketName, Key);
+
+  bool Result = !Key.IsEmpty();
+  if (Result && File->IsDirectory)
+  {
+    Key = GetFolderKey(Key);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+bool TS3FileSystem::DoLoadFileProperties(
+  const UnicodeString & AFileName, const TRemoteFile * File, TS3FileProperties & Properties)
+{
+  UnicodeString BucketName, Key;
+  bool Result = ParsePathForPropertiesRequests(AFileName, File, BucketName, Key);
+  if (Result)
+  {
+    TLibS3BucketContext BucketContext = GetBucketContext(BucketName, Key);
+
+    S3ResponseHandler ResponseHandler = CreateResponseHandler();
+
+    TLibS3CallbackData Data;
+    RequestInit(Data);
+
+    S3_get_acl(
+      &BucketContext, StrToS3(Key), Properties.OwnerId, Properties.OwnerDisplayName,
+      &Properties.AclGrantCount, Properties.AclGrants,
+      FRequestContext, FTimeout, &ResponseHandler, &Data);
+
+    CheckLibS3Error(Data);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+typedef std::vector<S3AclGrant> TAclGrantsVector;
+static void AddAclGrant(
+  TRights::TRightGroup Group, unsigned short & Permissions, TAclGrantsVector & AclGrants,
+  const S3AclGrant & AclGrantTemplate, S3Permission Permission)
+{
+  TRights::TRightLevel Level = S3PermissionToRightLevel(Permission);
+  TRights::TFlag Flag = TRights::CalculateFlag(Group, Level);
+  if (FLAGSET(Permissions, Flag))
+  {
+    S3AclGrant AclGrant(AclGrantTemplate);
+    AclGrant.permission = Permission;
+    AclGrants.push_back(AclGrant);
+    Permissions -= static_cast<short int>(Flag);
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TS3FileSystem::ChangeFileProperties(const UnicodeString FileName,
-  const TRemoteFile * /*File*/, const TRemoteProperties * /*Properties*/,
+  const TRemoteFile * File, const TRemoteProperties * Properties,
   TChmodSessionAction & /*Action*/)
 {
-  DebugFail();
+  TValidProperties ValidProperties = Properties->Valid;
+  if (DebugAlwaysTrue(ValidProperties.Contains(vpRights)))
+  {
+    ValidProperties >> vpRights;
+
+    DebugAssert(!Properties->AddXToDirectories);
+
+    TS3FileProperties FileProperties;
+    if (DebugAlwaysTrue(!File->IsDirectory) &&
+        DebugAlwaysTrue(DoLoadFileProperties(FileName, File, FileProperties)))
+    {
+      TAclGrantsVector NewAclGrants;
+
+      unsigned short Permissions = File->Rights->Combine(Properties->Rights);
+      for (int GroupI = TRights::rgFirst; GroupI <= TRights::rgLast; GroupI++)
+      {
+        TRights::TRightGroup Group = static_cast<TRights::TRightGroup>(GroupI);
+        S3AclGrant NewAclGrant;
+        memset(&NewAclGrant, 0, sizeof(NewAclGrant));
+        if (Group == TRights::rgUser)
+        {
+          NewAclGrant.granteeType = S3GranteeTypeCanonicalUser;
+          DebugAssert(sizeof(NewAclGrant.grantee.canonicalUser.id) == sizeof(FileProperties.OwnerId));
+          strcpy(NewAclGrant.grantee.canonicalUser.id, FileProperties.OwnerId);
+        }
+        else if (Group == TRights::rgS3AllAwsUsers)
+        {
+          NewAclGrant.granteeType = S3GranteeTypeAllAwsUsers;
+        }
+        else if (DebugAlwaysTrue(Group == TRights::rgS3AllUsers))
+        {
+          NewAclGrant.granteeType = S3GranteeTypeAllUsers;
+        }
+        unsigned short AllGroupPermissions =
+          TRights::CalculatePermissions(Group, TRights::rlS3Read, TRights::rlS3ReadACP, TRights::rlS3WriteACP);
+        if (FLAGSET(Permissions, AllGroupPermissions))
+        {
+          NewAclGrant.permission = S3PermissionFullControl;
+          NewAclGrants.push_back(NewAclGrant);
+          Permissions -= AllGroupPermissions;
+        }
+        else
+        {
+          #define ADD_ACL_GRANT(PERM) AddAclGrant(Group, Permissions, NewAclGrants, NewAclGrant, PERM)
+          ADD_ACL_GRANT(S3PermissionRead);
+          ADD_ACL_GRANT(S3PermissionWrite);
+          ADD_ACL_GRANT(S3PermissionReadACP);
+          ADD_ACL_GRANT(S3PermissionWriteACP);
+        }
+      }
+
+      DebugAssert(Permissions == 0);
+
+      // Preserve unrecognized permissions
+      for (int Index = 0; Index < FileProperties.AclGrantCount; Index++)
+      {
+        S3AclGrant & AclGrant = FileProperties.AclGrants[Index];
+        unsigned short Permission = AclGrantToPermissions(AclGrant, FileProperties);
+        if (Permission == 0)
+        {
+          NewAclGrants.push_back(AclGrant);
+        }
+      }
+
+      UnicodeString BucketName, Key;
+      if (DebugAlwaysTrue(ParsePathForPropertiesRequests(FileName, File, BucketName, Key)))
+      {
+        TLibS3BucketContext BucketContext = GetBucketContext(BucketName, Key);
+
+        S3ResponseHandler ResponseHandler = CreateResponseHandler();
+
+        TLibS3CallbackData Data;
+        RequestInit(Data);
+
+        S3_set_acl(
+          &BucketContext, StrToS3(Key), FileProperties.OwnerId, FileProperties.OwnerDisplayName,
+          NewAclGrants.size(), &NewAclGrants[0],
+          FRequestContext, FTimeout, &ResponseHandler, &Data);
+
+        CheckLibS3Error(Data);
+      }
+    }
+  }
+
+  DebugAssert(ValidProperties.Empty());
 }
 //---------------------------------------------------------------------------
-bool __fastcall TS3FileSystem::LoadFilesProperties(TStrings * /*FileList*/)
+unsigned short TS3FileSystem::AclGrantToPermissions(S3AclGrant & AclGrant, const TS3FileProperties & Properties)
 {
-  DebugFail();
-  return false;
+  TRights::TRightGroup RightGroup = static_cast<TRights::TRightGroup>(-1);
+  if (AclGrant.granteeType == S3GranteeTypeCanonicalUser)
+  {
+    if (strcmp(Properties.OwnerId, AclGrant.grantee.canonicalUser.id) == 0)
+    {
+      RightGroup = TRights::rgUser;
+    }
+    else
+    {
+      FTerminal->LogEvent(1, FORMAT(L"Unspported permission for canonical user %s", (StrFromS3(Properties.OwnerId))));
+    }
+  }
+  else if (AclGrant.granteeType == S3GranteeTypeAllAwsUsers)
+  {
+    RightGroup = TRights::rgS3AllAwsUsers;
+  }
+  else if (AclGrant.granteeType == S3GranteeTypeAllUsers)
+  {
+    RightGroup = TRights::rgS3AllUsers;
+  }
+  unsigned short Result;
+  if (RightGroup < 0)
+  {
+    Result = 0;
+  }
+  else
+  {
+    if (AclGrant.permission == S3PermissionFullControl)
+    {
+      Result = TRights::CalculatePermissions(RightGroup, TRights::rlS3Read, TRights::rlS3ReadACP, TRights::rlS3WriteACP);
+    }
+    else
+    {
+      DebugAssert(AclGrant.permission != S3PermissionWrite);
+      TRights::TRightLevel RightLevel = S3PermissionToRightLevel(AclGrant.permission);
+      if (RightLevel == TRights::rlNone)
+      {
+        Result = 0;
+      }
+      else
+      {
+        Result = TRights::CalculateFlag(RightGroup, RightLevel);
+      }
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TS3FileSystem::LoadFileProperties(const UnicodeString AFileName, const TRemoteFile * File, void * Param)
+{
+  bool & Result = *static_cast<bool *>(Param);
+  TS3FileProperties Properties;
+  Result = DoLoadFileProperties(AFileName, File, Properties);
+  if (Result)
+  {
+    bool AdditionalRights;
+    unsigned short Permissions = 0;
+    for (int Index = 0; Index < Properties.AclGrantCount; Index++)
+    {
+      S3AclGrant & AclGrant = Properties.AclGrants[Index];
+      unsigned short Permission = AclGrantToPermissions(AclGrant, Properties);
+      if (Permission == 0)
+      {
+        AdditionalRights = true;
+      }
+      else
+      {
+        Permissions |= Permission;
+      }
+    }
+
+    UnicodeString Delimiter(L",");
+    UnicodeString HumanRights;
+    for (int GroupI = TRights::rgFirst; GroupI <= TRights::rgLast; GroupI++)
+    {
+      TRights::TRightGroup Group = static_cast<TRights::TRightGroup>(GroupI);
+      #define RIGHT_LEVEL_SET(LEVEL) FLAGSET(Permissions, TRights::CalculateFlag(Group, TRights::LEVEL))
+      bool ReadRight = RIGHT_LEVEL_SET(rlS3Read);
+      bool WriteRight = DebugAlwaysFalse(RIGHT_LEVEL_SET(rlS3Write));
+      bool ReadACPRight = RIGHT_LEVEL_SET(rlS3ReadACP);
+      bool WriteACPRight = RIGHT_LEVEL_SET(rlS3WriteACP);
+      UnicodeString Desc;
+      if (ReadRight && ReadACPRight && WriteACPRight)
+      {
+        Desc = L"F";
+      }
+      else if (ReadRight)
+      {
+        Desc = L"R";
+        if (ReadACPRight || WriteACPRight || WriteRight)
+        {
+          Desc += L"+";
+        }
+      }
+
+      if (!Desc.IsEmpty())
+      {
+        UnicodeString GroupDesc;
+        switch (Group)
+        {
+          case TRights::rgUser: GroupDesc = L"O"; break;
+          case TRights::rgS3AllAwsUsers: GroupDesc = L"U"; break;
+          case TRights::rgS3AllUsers: GroupDesc = L"E"; break;
+          default: DebugFail(); break;
+        }
+
+        if (!GroupDesc.IsEmpty())
+        {
+          Desc = GroupDesc + L":" + Desc;
+          AddToList(HumanRights, Desc, Delimiter);
+        }
+      }
+    }
+
+    if (AdditionalRights)
+    {
+      AddToList(HumanRights, L"+", Delimiter);
+    }
+
+    File->Rights->Number = Permissions;
+    File->Rights->SetTextOverride(HumanRights);
+    Result = true;
+  }
+}
+//---------------------------------------------------------------------------
+bool __fastcall TS3FileSystem::LoadFilesProperties(TStrings * FileList)
+{
+  bool Result = false;
+  FTerminal->BeginTransaction();
+  try
+  {
+    FTerminal->ProcessFiles(FileList, foGetProperties, LoadFileProperties, &Result);
+  }
+  __finally
+  {
+    FTerminal->EndTransaction();
+  }
+  return Result;
 }
 //---------------------------------------------------------------------------
 void __fastcall TS3FileSystem::CalculateFilesChecksum(const UnicodeString & /*Alg*/,

+ 8 - 0
source/core/S3FileSystem.h

@@ -13,6 +13,7 @@ struct TLibS3TransferObjectDataCallbackData;
 struct TLibS3PutObjectDataCallbackData;
 struct TLibS3GetObjectDataCallbackData;
 struct ssl_st;
+struct TS3FileProperties;
 #ifdef NEED_LIBS3
 // resolve clash
 #define S3Protocol _S3Protocol
@@ -26,8 +27,10 @@ struct S3RequestContext;
 struct S3ErrorDetails;
 struct S3ListBucketContent;
 struct S3ResponseHandler;
+struct S3AclGrant;
 enum S3Status { };
 enum _S3Protocol { };
+enum S3Permission { };
 #endif
 //------------------------------------------------------------------------------
 class TS3FileSystem : public TCustomFileSystem
@@ -158,6 +161,11 @@ protected:
   S3Status GetObjectData(int BufferSize, const char * Buffer, TLibS3GetObjectDataCallbackData & Data);
   bool ShouldCancelTransfer(TLibS3TransferObjectDataCallbackData & Data);
   bool IsGoogleCloud();
+  void __fastcall LoadFileProperties(const UnicodeString AFileName, const TRemoteFile * File, void * Param);
+  bool DoLoadFileProperties(const UnicodeString & AFileName, const TRemoteFile * File, TS3FileProperties & Properties);
+  unsigned short AclGrantToPermissions(S3AclGrant & AclGrant, const TS3FileProperties & Properties);
+  bool ParsePathForPropertiesRequests(
+    const UnicodeString & Path, const TRemoteFile * File, UnicodeString & BucketName, UnicodeString & Key);
 
   static TS3FileSystem * GetFileSystem(void * CallbackData);
   static void LibS3SessionCallback(ne_session_s * Session, void * CallbackData);

+ 1 - 0
source/core/ScpFileSystem.cpp

@@ -447,6 +447,7 @@ bool __fastcall TSCPFileSystem::IsCapable(int Capability) const
     case fcTextMode:
       return FTerminal->SessionData->EOLType != FTerminal->Configuration->LocalEOLType;
 
+    case fcAclChangingFiles:
     case fcNativeTextMode:
     case fcNewerOnlyUpload:
     case fcTimestampChanging:

+ 1 - 1
source/core/SessionInfo.h

@@ -32,7 +32,7 @@ struct TSessionInfo
   bool CertificateVerifiedManually;
 };
 //---------------------------------------------------------------------------
-enum TFSCapability { fcUserGroupListing, fcModeChanging, fcGroupChanging,
+enum TFSCapability { fcUserGroupListing, fcModeChanging, fcAclChangingFiles, fcGroupChanging,
   fcOwnerChanging, fcGroupOwnerChangingByID, fcAnyCommand, fcHardLink,
   fcSymbolicLink,
   // With WebDAV this is always true, to avoid double-click on

+ 2 - 3
source/core/SftpFileSystem.cpp

@@ -491,9 +491,7 @@ public:
       if (Properties->Valid.Contains(vpRights))
       {
         Valid = (TValid)(Valid | valRights);
-        TRights Rights = BaseRights;
-        Rights |= Properties->Rights.NumberSet;
-        Rights &= (unsigned short)~Properties->Rights.NumberUnset;
+        TRights Rights = TRights(BaseRights).Combine(Properties->Rights);
         if (IsDirectory && Properties->AddXToDirectories)
         {
           Rights.AddExecute();
@@ -2073,6 +2071,7 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
     case fcAnyCommand:
     case fcShellAnyCommand:
     case fcLocking:
+    case fcAclChangingFiles: // pending implementation
       return false;
 
     case fcNewerOnlyUpload:

+ 1 - 0
source/core/WebDAVFileSystem.cpp

@@ -582,6 +582,7 @@ bool __fastcall TWebDAVFileSystem::IsCapable(int Capability) const
     case fcUserGroupListing:
     case fcModeChanging:
     case fcModeChangingUpload:
+    case fcAclChangingFiles:
     case fcGroupChanging:
     case fcOwnerChanging:
     case fcAnyCommand:

+ 47 - 12
source/forms/CustomScpExplorer.cpp

@@ -2898,16 +2898,35 @@ bool __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Oper
   TOperationSide Side, bool OnFocused, bool NoConfirmation, void * Param)
 {
   Side = GetSide(Side);
+  bool FullPath = IsSideLocalBrowser(Side);
+  TCustomDirView * ADirView = DirView(Side);
+  std::unique_ptr<TStringList> FileList(new TStringList());
+  ADirView->CreateFileList(OnFocused, FullPath, FileList.get());
 
-  bool Result;
-  TStrings * FileList = DirView(Side)->CreateFileList(OnFocused, IsSideLocalBrowser(Side), NULL);
-  try
-  {
-    Result = ExecuteFileOperation(Operation, Side, FileList, NoConfirmation, Param);
-  }
-  __finally
+  bool ReloadProperties =
+    (Side == osRemote) && (Operation == foSetProperties) &&
+    DebugAlwaysTrue(HasActiveTerminal()) && Terminal->IsCapable[fcLoadingAdditionalProperties];
+
+  bool Result = ExecuteFileOperation(Operation, Side, FileList.get(), NoConfirmation, Param);
+
+  if (Result && ReloadProperties)
   {
-    delete FileList;
+    std::unique_ptr<TStringList> FileList2(new TStringList());
+
+    FileList->CaseSensitive = true;
+    FileList->Sort();
+    for (int Index = 0; Index < ADirView->Items->Count; Index++)
+    {
+      TListItem * Item = ADirView->Items->Item[Index];
+      UnicodeString FileName = ADirView->ItemFileName(Item);
+      int Index2;
+      if (FileList->Find(FileName, Index2))
+      {
+        FileList2->AddObject(FileName, ADirView->ItemData(Item));
+      }
+    }
+
+    LoadFilesProperties(FileList2.get());
   }
   return Result;
 }
@@ -4347,6 +4366,14 @@ void __fastcall TCustomScpExplorerForm::CalculateChecksum(const UnicodeString &
   }
 }
 //---------------------------------------------------------------------------
+void TCustomScpExplorerForm::LoadFilesProperties(TStrings * FileList)
+{
+  if (Terminal->LoadFilesProperties(FileList))
+  {
+    RemoteDirView->Invalidate();
+  }
+}
+//---------------------------------------------------------------------------
 bool __fastcall TCustomScpExplorerForm::SetProperties(TOperationSide Side, TStrings * FileList)
 {
   bool Result;
@@ -4359,10 +4386,7 @@ bool __fastcall TCustomScpExplorerForm::SetProperties(TOperationSide Side, TStri
     {
       TRemoteProperties CurrentProperties;
 
-      if (Terminal->LoadFilesProperties(FileList))
-      {
-        RemoteDirView->Invalidate();
-      }
+      LoadFilesProperties(FileList);
 
       bool CapableGroupChanging = Terminal->IsCapable[fcGroupChanging];
       bool CapableOwnerChanging = Terminal->IsCapable[fcOwnerChanging];
@@ -4417,8 +4441,19 @@ bool __fastcall TCustomScpExplorerForm::SetProperties(TOperationSide Side, TStri
 
       CurrentProperties = TRemoteProperties::CommonProperties(FileList);
 
+      bool CapableAclChanging = Terminal->IsCapable[fcAclChangingFiles];
+      for (int Index = 0; (Index < FileList->Count) && CapableAclChanging; Index++)
+      {
+        if (dynamic_cast<TRemoteFile *>(FileList->Objects[Index])->IsDirectory)
+        {
+          CapableAclChanging = false;
+          CurrentProperties.Valid = CurrentProperties.Valid >> vpRights;
+        }
+      }
+
       int Flags = 0;
       if (Terminal->IsCapable[fcModeChanging]) Flags |= cpMode;
+      if (CapableAclChanging) Flags |= cpAcl;
       if (CapableOwnerChanging) Flags |= cpOwner;
       if (CapableGroupChanging) Flags |= cpGroup;
 

+ 1 - 0
source/forms/CustomScpExplorer.h

@@ -716,6 +716,7 @@ protected:
   void __fastcall DetachTerminal(TObject * ATerminal);
   bool __fastcall IsActiveTerminal(TTerminal * Terminal);
   void __fastcall UpdateDarkMode();
+  void LoadFilesProperties(TStrings * FileList);
 
 public:
   virtual __fastcall ~TCustomScpExplorerForm();

+ 1 - 0
source/forms/FileSystemInfo.cpp

@@ -118,6 +118,7 @@ void __fastcall TFileSystemInfoDialog::Feed(TFeedFileSystemData AddItem)
   AddItem(CertificateFingerprintSha1Edit, 0, FSessionInfo.CertificateFingerprintSHA1);
 
   AddItem(ProtocolView, FSINFO_MODE_CHANGING, CapabilityStr(fcModeChanging));
+  AddItem(ProtocolView, FSINFO_ACL_CHANGING, CapabilityStr(fcAclChangingFiles));
   AddItem(ProtocolView, FSINFO_OWNER_GROUP_CHANGING, CapabilityStr(fcGroupChanging));
   UnicodeString AnyCommand;
   if (!FFileSystemInfo.IsCapable[fcShellAnyCommand] &&

+ 34 - 20
source/forms/Properties.cpp

@@ -55,6 +55,12 @@ __fastcall TPropertiesDialog::TPropertiesDialog(TComponent* AOwner,
   FFileList = new TStringList();
   FFileList->Assign(FileList);
   FAllowedChanges = AllowedChanges;
+  if (FLAGSET(FAllowedChanges, cpAcl))
+  {
+    DebugAssert(FLAGCLEAR(FAllowedChanges, cpMode));
+    RightsLabel->Caption = LoadStr(PROPERTIES_ACL);
+    RightsFrame->DisplayAsAcl();
+  }
   FUserGroupByID = UserGroupByID;
 
   FAllowCalculateStats = false;
@@ -173,17 +179,14 @@ UnicodeString __fastcall TPropertiesDialog::LoadRemoteToken(
 void __fastcall TPropertiesDialog::LoadRemoteToken(
   TComboBox * ComboBox, TEdit * View, TLabel * Label, bool Valid, const TRemoteToken & Token, int Change)
 {
-  if (Valid)
-  {
-    UnicodeString Value = LoadRemoteToken(Token);
-    ComboBox->Text = Value;
-    View->Text = Value;
-  }
+  UnicodeString Value = Valid ? LoadRemoteToken(Token) : EmptyStr;
+  ComboBox->Text = Value;
+  View->Text = Value;
   bool AllowedChange = FLAGSET(FAllowedChanges, Change);
-  ComboBox->Visible = Valid && AllowedChange;
+  ComboBox->Visible = AllowedChange;
   View->Visible = Valid && !AllowedChange;
-  Label->Visible = Valid;
   Label->FocusControl = AllowedChange ? static_cast<TWinControl *>(ComboBox) : static_cast<TWinControl *>(View);
+  Label->Visible = Label->FocusControl->Visible;
 }
 //---------------------------------------------------------------------------
 void __fastcall TPropertiesDialog::LoadRemoteTokens(TComboBox * ComboBox,
@@ -383,9 +386,18 @@ void __fastcall TPropertiesDialog::LoadStats(__int64 FilesSize,
 void __fastcall TPropertiesDialog::SetFileProperties(const TRemoteProperties & value)
 {
   TValidProperties Valid;
-  if (value.Valid.Contains(vpRights) && FAllowedChanges & cpMode) Valid << vpRights;
-  if (value.Valid.Contains(vpOwner) && FAllowedChanges & cpOwner) Valid << vpOwner;
-  if (value.Valid.Contains(vpGroup) && FAllowedChanges & cpGroup) Valid << vpGroup;
+  if (value.Valid.Contains(vpRights) && (FLAGSET(FAllowedChanges, cpMode) || FLAGSET(FAllowedChanges, cpAcl)))
+  {
+    Valid << vpRights;
+  }
+  if (value.Valid.Contains(vpOwner) && FLAGSET(FAllowedChanges, cpOwner))
+  {
+    Valid << vpOwner;
+  }
+  if (value.Valid.Contains(vpGroup) && FLAGSET(FAllowedChanges, cpGroup))
+  {
+    Valid << vpGroup;
+  }
   FOrigProperties = value;
   FOrigProperties.Valid = Valid;
   FOrigProperties.Recursive = false;
@@ -409,14 +421,13 @@ void __fastcall TPropertiesDialog::SetFileProperties(const TRemoteProperties & v
   LoadRemoteToken(GroupComboBox, GroupView, GroupLabel, value.Valid.Contains(vpGroup), value.Group, cpGroup);
   LoadRemoteToken(OwnerComboBox, OwnerView, OwnerLabel, value.Valid.Contains(vpOwner), value.Owner, cpOwner);
 
-  bool HasGroupOrOwner = GroupLabel->Visible || OwnerLabel->Visible;
-  bool HasAnything = HasGroupOrOwner || HasRights;
+  bool HasAnything = GroupLabel->Visible || OwnerLabel->Visible || HasRights;
   // Not necesarily true, let's find the scenario when it is not and then decide how to render that (shift rights up?)
-  DebugAssert(HasGroupOrOwner || !HasRights);
+  DebugAssert((GroupLabel->Visible || OwnerLabel->Visible) || !HasRights);
   GroupOwnerRightsBevel->Visible = HasAnything;
 
   RecursiveCheck2->Checked = value.Recursive;
-  RecursiveCheck2->Visible = HasAnything && FAnyDirectories;
+  RecursiveCheck2->Visible = (GroupComboBox->Visible || OwnerComboBox->Visible) && FAnyDirectories;
   RecursiveBevel->Visible = RecursiveCheck2->Visible || HasRights;
 
   UpdateControls();
@@ -517,7 +528,7 @@ TRemoteProperties __fastcall TPropertiesDialog::GetFileProperties()
 {
   TRemoteProperties Result;
 
-  if (FAllowedChanges & cpMode)
+  if (FLAGSET(FAllowedChanges, cpMode) || FLAGSET(FAllowedChanges, cpAcl))
   {
     Result.Valid << vpRights;
     Result.Rights = RightsFrame->Rights;
@@ -546,7 +557,10 @@ void __fastcall TPropertiesDialog::UpdateControls()
 {
   // No point enabling recursive check if there's no change allowed (supported),
   // i.e. with WebDAV.
-  EnableControl(RecursiveCheck2, ((FAllowedChanges & (cpGroup | cpOwner | cpMode)) != 0));
+  bool AnyAllowedChanges =
+    FLAGSET(FAllowedChanges, cpGroup) || FLAGSET(FAllowedChanges, cpOwner) ||
+    FLAGSET(FAllowedChanges, cpMode) || FLAGSET(FAllowedChanges, cpAcl);
+  EnableControl(RecursiveCheck2, AnyAllowedChanges);
 
   bool Allow;
   try
@@ -565,9 +579,9 @@ void __fastcall TPropertiesDialog::UpdateControls()
   }
   EnableControl(OkButton, Allow);
 
-  EnableControl(GroupComboBox, FAllowedChanges & cpGroup);
-  EnableControl(OwnerComboBox, FAllowedChanges & cpOwner);
-  EnableControl(RightsFrame, FAllowedChanges & cpMode);
+  EnableControl(GroupComboBox, FLAGSET(FAllowedChanges, cpGroup));
+  EnableControl(OwnerComboBox, FLAGSET(FAllowedChanges, cpOwner));
+  EnableControl(RightsFrame, FLAGSET(FAllowedChanges, cpMode) || FLAGSET(FAllowedChanges, cpAcl));
   CalculateSizeButton->Visible = FAllowCalculateStats;
 
   if (!FMultiple)

+ 9 - 0
source/forms/Properties.dfm

@@ -142,6 +142,15 @@ object PropertiesDialog: TPropertiesDialog
         Anchors = [akLeft, akTop, akRight]
         Shape = bsTopLine
       end
+      object AclLabel: TLabel
+        Left = 8
+        Top = 203
+        Width = 59
+        Height = 13
+        Caption = 'Permissions:'
+        FocusControl = RightsFrame
+        Visible = False
+      end
       object LocationLabel: TEdit
         Left = 85
         Top = 58

+ 1 - 0
source/forms/Properties.h

@@ -58,6 +58,7 @@ __published:
   TLabel *ChecksumUnknownLabel;
   TEdit *OwnerView;
   TEdit *GroupView;
+  TLabel *AclLabel;
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall FormCloseQuery(TObject *Sender, bool &CanClose);
   void __fastcall CalculateSizeButtonClick(TObject *Sender);

+ 98 - 41
source/forms/Rights.cpp

@@ -8,7 +8,7 @@
 
 #include <VCLCommon.h>
 #include <Tools.h>
-#include <GUITools.h>
+#include <TextsWin.h>
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 #pragma link "GrayedCheckBox"
@@ -29,6 +29,7 @@ __fastcall TRightsFrame::TRightsFrame(TComponent* Owner)
   PopupMenu = RightsPopup;
   FPopingContextMenu = false;
   FInitialized = false;
+  FAcl = false;
 
   #define COPY_HINT(R) \
     Checks[TRights::rrGroup ## R]->Hint = Checks[TRights::rrUser ## R]->Hint; \
@@ -192,52 +193,70 @@ void __fastcall TRightsFrame::UpdateControls()
   DoChange();
 }
 //---------------------------------------------------------------------------
-void __fastcall TRightsFrame::CycleRights(int Group)
+void TRightsFrame::CycleRights(TRights::TRightGroup RightGroup)
 {
-  TRights::TState State;
+  int Last = FAcl ? TRights::rlLastAcl : TRights::rlLastNormal;
+  TRights::TState State = TRights::TState(); // shut up
+  bool Any = false;
   bool Same = true;
-  for (int Right = 0; Right < 3; Right++)
+  for (int RightLevelI = TRights::rlFirst; RightLevelI <= Last; RightLevelI++)
   {
-    TRights::TState CState = States[static_cast<TRights::TRight>(
-      TRights::rrUserRead + Right + ((Group - 1) * 3))];
+    TRights::TRightLevel RightLevel = static_cast<TRights::TRightLevel>(RightLevelI);
+    TRights::TRight Right = TRights::CalculateRight(RightGroup, RightLevel);
 
-    if (Right == 0) State = CState;
-      else
-    if (State != CState) Same = False;
+    if (Checks[Right]->Visible)
+    {
+      TRights::TState RightState = States[Right];
+      if (!Any)
+      {
+        State = RightState;
+        Any = true;
+      }
+      else if (State != RightState)
+      {
+        Same = false;
+        break;
+      }
+    }
   }
 
-  if (!Same)
-  {
-    State = TRights::rsYes;
-  }
-  else
+  if (DebugAlwaysTrue(Any))
   {
-    switch (State) {
-      case TRights::rsYes:
-        State = TRights::rsNo;
-        break;
+    if (!Same)
+    {
+      State = TRights::rsYes;
+    }
+    else
+    {
+      switch (State)
+      {
+        case TRights::rsYes:
+          State = TRights::rsNo;
+          break;
 
-      case TRights::rsNo:
-        State = AllowUndef ? TRights::rsUndef : TRights::rsYes;
-        break;
+        case TRights::rsNo:
+          State = AllowUndef ? TRights::rsUndef : TRights::rsYes;
+          break;
 
-      case TRights::rsUndef:
-        State = TRights::rsYes;
-        break;
+        case TRights::rsUndef:
+          State = TRights::rsYes;
+          break;
+      }
     }
-  }
 
-  for (int Right = 0; Right < 3; Right++)
-  {
-    States[static_cast<TRights::TRight>(
-      TRights::rrUserRead + Right + ((Group - 1) * 3))] = State;
+    for (int RightLevelI = TRights::rlFirst; RightLevelI <= Last; RightLevelI++)
+    {
+      TRights::TRightLevel RightLevel = static_cast<TRights::TRightLevel>(RightLevelI);
+      TRights::TRight Right = TRights::CalculateRight(RightGroup, RightLevel);
+      States[Right] = State;
+    }
+    UpdateControls();
   }
-  UpdateControls();
 }
 //---------------------------------------------------------------------------
-void __fastcall TRightsFrame::RightsButtonsClick(TObject *Sender)
+void __fastcall TRightsFrame::RightsButtonsClick(TObject * Sender)
 {
-  CycleRights(((TComponent*)Sender)->Tag);
+  CycleRights(static_cast<TRights::TRightGroup>(dynamic_cast<TComponent *>(Sender)->Tag - 1));
 }
 //---------------------------------------------------------------------------
 void __fastcall TRightsFrame::SetAllowAddXToDirectories(bool value)
@@ -358,37 +377,37 @@ void __fastcall TRightsFrame::RightsActionsUpdate(TBasicAction *Action,
   {
     // explicitly enable as action gets disabled, if its shortcut is pressed,
     // while the form does not have a focus and OnExecute handler denies the execution
-    NoRightsAction->Enabled = true;
+    NoRightsAction->Enabled = !FAcl;
     NoRightsAction->Checked = !R.IsUndef && (R.NumberSet == TRights::rfNo);
   }
   else if (Action == DefaultRightsAction)
   {
-    DefaultRightsAction->Enabled = true;
+    DefaultRightsAction->Enabled = !FAcl;
     DefaultRightsAction->Checked = !R.IsUndef && (R.NumberSet == TRights::rfDefault);
   }
   else if (Action == AllRightsAction)
   {
-    AllRightsAction->Enabled = true;
+    AllRightsAction->Enabled = !FAcl;
     AllRightsAction->Checked = !R.IsUndef && (R.NumberSet == TRights::rfAll);
   }
   else if (Action == LeaveRightsAsIsAction)
   {
-    LeaveRightsAsIsAction->Enabled = true;
+    LeaveRightsAsIsAction->Enabled = !FAcl;
     LeaveRightsAsIsAction->Visible = R.AllowUndef;
     LeaveRightsAsIsAction->Checked = (R.NumberSet == TRights::rfNo) &&
       (R.NumberUnset == TRights::rfNo);
   }
   else if (Action == CopyTextAction)
   {
-    CopyTextAction->Enabled = !R.IsUndef;
+    CopyTextAction->Enabled = !FAcl && !R.IsUndef;
   }
   else if (Action == CopyOctalAction)
   {
-    CopyOctalAction->Enabled = !R.IsUndef;
+    CopyOctalAction->Enabled = !FAcl && !R.IsUndef;
   }
   else if (Action == PasteAction)
   {
-    PasteAction->Enabled = IsFormatInClipboard(CF_TEXT);
+    PasteAction->Enabled = !FAcl && IsFormatInClipboard(CF_TEXT);
   }
   else
   {
@@ -633,8 +652,15 @@ void __fastcall TRightsFrame::RightsPopupPopup(TObject * /*Sender*/)
 void __fastcall TRightsFrame::FrameContextPopup(TObject * Sender,
   TPoint & MousePos, bool & Handled)
 {
-  SelectScaledImageList(RightsImages);
-  MenuPopup(Sender, MousePos, Handled);
+  if (!FAcl)
+  {
+    SelectScaledImageList(RightsImages);
+    MenuPopup(Sender, MousePos, Handled);
+  }
+  else
+  {
+    Handled = true;
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TRightsFrame::UpdateByOctal()
@@ -707,3 +733,34 @@ void __fastcall TRightsFrame::CloseButtonClick(TObject *)
   CloseUp();
 }
 //---------------------------------------------------------------------------
+void TRightsFrame::DisplayAsAcl(TRights::TRight ReadRight, TRights::TRight WriteRight, TRights::TRight ExecRight, TRights::TRight SpecialRight)
+{
+  TCheckBox * ReadCheck = Checks[ReadRight];
+  TCheckBox * WriteCheck = Checks[WriteRight];
+  TCheckBox * ReadAclCheck = Checks[ExecRight];
+  TCheckBox * WriteAclCheck = Checks[SpecialRight];
+
+  WriteCheck->Visible = false;
+
+  int ReadAclLeft = (3*ReadCheck->Left + 2*WriteAclCheck->Left) / 5;
+  int Shift = ReadAclLeft - ReadAclCheck->Left;
+  ReadAclCheck->Left = ReadAclLeft;
+  ReadAclCheck->Width = ReadAclCheck->Width - Shift;
+  ReadAclCheck->Caption = L"R ACL";
+
+  WriteAclCheck->Caption = L"W ACL";
+
+  GroupLabel->Caption = LoadStr(PROPERTIES_S3_USERS);
+  OthersLabel->Caption = LoadStr(PROPERTIES_S3_EVERYONE);
+}
+//---------------------------------------------------------------------------
+void TRightsFrame::DisplayAsAcl()
+{
+  AllowAddXToDirectories = false;
+  OctalLabel->Visible = false;
+  OctalEdit->Visible = false;
+  DisplayAsAcl(TRights::rrUserRead, TRights::rrUserWrite, TRights::rrUserExec, TRights::rrUserIDExec);
+  DisplayAsAcl(TRights::rrGroupRead, TRights::rrGroupWrite, TRights::rrGroupExec, TRights::rrGroupIDExec);
+  DisplayAsAcl(TRights::rrOtherRead, TRights::rrOtherWrite, TRights::rrOtherExec, TRights::rrStickyBit);
+  FAcl = true;
+}

+ 4 - 1
source/forms/Rights.h

@@ -84,8 +84,9 @@ private:
   bool FPopingContextMenu;
   UnicodeString FAddXToDirectoriesSuffix;
   bool FInitialized;
+  bool FAcl;
 
-  void __fastcall CycleRights(int Group);
+  void CycleRights(TRights::TRightGroup RightGroup);
   bool __fastcall GetAddXToDirectories();
   bool __fastcall GetAllowUndef();
   TCheckBox * __fastcall GetChecks(TRights::TRight Right);
@@ -104,6 +105,7 @@ public:
   __fastcall TRightsFrame(TComponent* Owner);
   void __fastcall DropDown();
   void __fastcall CloseUp();
+  void DisplayAsAcl();
 
   __property bool AddXToDirectories = { read = GetAddXToDirectories, write = SetAddXToDirectories };
   __property bool AllowAddXToDirectories = { read = FAllowAddXToDirectories, write = SetAllowAddXToDirectories };
@@ -134,6 +136,7 @@ protected:
   bool __fastcall DirectoriesXEffective();
   void __fastcall UpdateOctalEdit();
   void __fastcall UpdateByOctal();
+  void DisplayAsAcl(TRights::TRight ReadRight, TRights::TRight WriteRight, TRights::TRight ExecRight, TRights::TRight SpecialRight);
 
   INTERFACE_HOOK_CUSTOM(TFrame);
 

+ 4 - 0
source/resource/TextsWin.h

@@ -641,6 +641,10 @@
 #define SYNCHRONIZE_CHECKLIST_DELETE_LOCAL 6032
 #define SYNCHRONIZE_CHECKLIST_REVERSE 6033
 #define IMPORT_CONVERTED_KEYS   6045
+#define FSINFO_ACL_CHANGING     6046
+#define PROPERTIES_ACL          6047
+#define PROPERTIES_S3_USERS     6048
+#define PROPERTIES_S3_EVERYONE  6049
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 4 - 0
source/resource/TextsWin1.rc

@@ -646,6 +646,10 @@ BEGIN
         SYNCHRONIZE_CHECKLIST_DELETE_LOCAL, "Delete obsolete local file"
         SYNCHRONIZE_CHECKLIST_REVERSE, "Click to reverse"
         IMPORT_CONVERTED_KEYS, "%d key file(s) in %d imported session(s) were converted or replaced with existing key(s) in supported format."
+        FSINFO_ACL_CHANGING, "Can change ACL"
+        PROPERTIES_ACL, "ACL:"
+        PROPERTIES_S3_USERS, "&Users"
+        PROPERTIES_S3_EVERYONE, "&Everyone"
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000–2021 Martin Prikryl"

+ 1 - 0
source/windows/WinInterface.h

@@ -297,6 +297,7 @@ struct TCalculateSizeStats;
 const cpMode =  0x01;
 const cpOwner = 0x02;
 const cpGroup = 0x04;
+const cpAcl =   0x08;
 typedef void __fastcall (__closure *TCalculateSizeEvent)
   (TStrings * FileList, __int64 & Size, TCalculateSizeStats & Stats,
    bool & Close);