1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00

Bug 1641: Support ACL for S3

https://winscp.net/tracker/1641

Source commit: 03a0b16e1a553b6a2791a2276e94a4547ab3451f
This commit is contained in:
Martin Prikryl
2021-10-29 15:33:37 +00:00
parent 7c4743c33d
commit 9b304c1b32
21 changed files with 601 additions and 90 deletions

View File

@@ -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,

View File

@@ -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:

View File

@@ -2058,11 +2058,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)
@@ -2246,6 +2282,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())
@@ -2330,6 +2375,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;
}
@@ -2539,6 +2585,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;
@@ -2569,6 +2616,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()
{

View File

@@ -325,6 +325,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,
@@ -338,13 +349,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);
@@ -352,6 +368,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;

View File

@@ -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:
@@ -1316,17 +1317,314 @@ void __fastcall TS3FileSystem::CreateLink(const UnicodeString FileName,
DebugFail();
}
//---------------------------------------------------------------------------
void __fastcall TS3FileSystem::ChangeFileProperties(const UnicodeString FileName,
const TRemoteFile * /*File*/, const TRemoteProperties * /*Properties*/,
TChmodSessionAction & /*Action*/)
struct TS3FileProperties
{
DebugFail();
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 __fastcall TS3FileSystem::LoadFilesProperties(TStrings * /*FileList*/)
bool TS3FileSystem::ParsePathForPropertiesRequests(
const UnicodeString & Path, const TRemoteFile * File, UnicodeString & BucketName, UnicodeString & Key)
{
DebugFail();
return false;
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,
TChmodSessionAction & /*Action*/)
{
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());
}
//---------------------------------------------------------------------------
unsigned short TS3FileSystem::AclGrantToPermissions(S3AclGrant & AclGrant, const TS3FileProperties & Properties)
{
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*/,

View File

@@ -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);

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -2967,16 +2967,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
bool ReloadProperties =
(Side == osRemote) && (Operation == foSetProperties) &&
DebugAlwaysTrue(HasActiveTerminal()) && Terminal->IsCapable[fcLoadingAdditionalProperties];
bool Result = ExecuteFileOperation(Operation, Side, FileList.get(), NoConfirmation, Param);
if (Result && ReloadProperties)
{
Result = ExecuteFileOperation(Operation, Side, FileList, NoConfirmation, Param);
}
__finally
{
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;
}
@@ -4483,6 +4502,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;
@@ -4495,10 +4522,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];
@@ -4553,8 +4577,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;

View File

@@ -740,6 +740,7 @@ protected:
void __fastcall DirectorySizeCalculated(TOperationSide Side, const UnicodeString & FileName, bool Success);
TListItem * VisualiseOperationFinished(TOperationSide Side, const UnicodeString & FileName, bool Unselect);
void __fastcall FileDeleted(TOperationSide Side, const UnicodeString & FileName, bool Success);
void LoadFilesProperties(TStrings * FileList);
public:
virtual __fastcall ~TCustomScpExplorerForm();

View File

@@ -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] &&

View File

@@ -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)

View File

@@ -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

View File

@@ -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);

View File

@@ -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 (!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::rsUndef:
State = TRights::rsYes;
if (Checks[Right]->Visible)
{
TRights::TState RightState = States[Right];
if (!Any)
{
State = RightState;
Any = true;
}
else if (State != RightState)
{
Same = false;
break;
}
}
}
for (int Right = 0; Right < 3; Right++)
if (DebugAlwaysTrue(Any))
{
States[static_cast<TRights::TRight>(
TRights::rrUserRead + Right + ((Group - 1) * 3))] = State;
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::rsUndef:
State = TRights::rsYes;
break;
}
}
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;
}

View File

@@ -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);

View File

@@ -653,6 +653,10 @@
#define NEW_REMOTE_TAB_HINT 6043
#define NEW_REMOTE_TAB_CTRL_HINT 6044
#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

View File

@@ -658,6 +658,10 @@ BEGIN
NEW_REMOTE_TAB_HINT, "Click to open new session in new remote tab.\nHold down Shift Key to open new session in new window."
NEW_REMOTE_TAB_CTRL_HINT, "%s\nHold down Ctrl key to open new local tab."
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"

View File

@@ -307,6 +307,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);