mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
Not scanning local directory symlinks pointing to network drives
Source commit: 19baa413392caabbb7afea6eb2b925cc98ca5076
This commit is contained in:
@@ -3,9 +3,16 @@
|
||||
#pragma hdrstop
|
||||
|
||||
#include <Common.h>
|
||||
#include <System.DateUtils.hpp>
|
||||
#include <System.IOUtils.hpp>
|
||||
#include "CoreMain.h"
|
||||
#include "DriveView.h"
|
||||
#include "IEDriveInfo.h"
|
||||
#include "DirView.h"
|
||||
#ifdef DESIGN_ONLY
|
||||
#undef AppLogFmt
|
||||
#define AppLogFmt(S, F)
|
||||
#endif
|
||||
//---------------------------------------------------------------------------
|
||||
#pragma package(smart_init)
|
||||
//---------------------------------------------------------------------------
|
||||
@@ -73,10 +80,16 @@ void __fastcall TDriveView::CreateWnd()
|
||||
void __fastcall TDriveView::ReadSubDirs(TTreeNode * Node)
|
||||
{
|
||||
auto NodeData = static_cast<TNodeData *>(Node->Data);
|
||||
TDateTime Start = Now();
|
||||
UnicodeString Path = NodePath(Node);
|
||||
TSearchRec SRec;
|
||||
if (!FindFirstSubDir(IncludeTrailingBackslash(Path) + L"*.*", SRec))
|
||||
{
|
||||
int Sec = static_cast<int>(SecondsBetween(Now(), Start));
|
||||
if (Sec > 2)
|
||||
{
|
||||
AppLogFmt(L"Took %d s to find that '%s' cannot be read", (Sec, Path));
|
||||
}
|
||||
Node->HasChildren = false;
|
||||
}
|
||||
else
|
||||
@@ -88,7 +101,7 @@ void __fastcall TDriveView::ReadSubDirs(TTreeNode * Node)
|
||||
Limit /= 10;
|
||||
CheckInterval /= 10;
|
||||
}
|
||||
if (!ReadSubDirsBatch(Node, SRec, CheckInterval, Limit))
|
||||
if (!ReadSubDirsBatch(Node, SRec, CheckInterval, Limit, Start))
|
||||
{
|
||||
NodeData->DelayedSrec = SRec;
|
||||
NodeData->DelayedExclude = new TStringList();
|
||||
@@ -108,7 +121,8 @@ void __fastcall TDriveView::ReadSubDirs(TTreeNode * Node)
|
||||
//---------------------------------------------------------------------------
|
||||
bool __fastcall TDriveView::DoScanDir(TTreeNode * FromNode)
|
||||
{
|
||||
return !static_cast<TNodeData *>(FromNode->Data)->IsRecycleBin;
|
||||
auto NodeData = static_cast<TNodeData *>(FromNode->Data);
|
||||
return !NodeData->IsRecycleBin && !NodeData->IsRemote;
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
void __fastcall TDriveView::AddChildNode(TTreeNode * ParentNode, UnicodeString ParentPath, const TSearchRec & SRec)
|
||||
@@ -116,19 +130,32 @@ void __fastcall TDriveView::AddChildNode(TTreeNode * ParentNode, UnicodeString P
|
||||
auto NodeData = new TNodeData();
|
||||
NodeData->Attr = SRec.Attr;
|
||||
NodeData->DirName = SRec.Name;
|
||||
UnicodeString Path = IncludeTrailingBackslash(ParentPath) + SRec.Name;
|
||||
NodeData->IsRecycleBin =
|
||||
FLAGSET(SRec.Attr, faSysFile) &&
|
||||
(ParentNode->Parent == nullptr) &&
|
||||
(SameText(SRec.Name, L"RECYCLED") ||
|
||||
SameText(SRec.Name, L"RECYCLER") ||
|
||||
SameText(SRec.Name, L"$RECYCLE.BIN"));
|
||||
|
||||
#ifndef DESIGN_ONLY
|
||||
if (FLAGSET(SRec.Attr, faSymLink))
|
||||
{
|
||||
String Target;
|
||||
NodeData->IsRemote =
|
||||
FileGetSymLinkTargetSafe(Path, Target) &&
|
||||
TPath::IsUNCRooted(Target);
|
||||
}
|
||||
#endif
|
||||
|
||||
NodeData->Scanned = false;
|
||||
|
||||
TTreeNode * NewNode = Items->AddChildObject(ParentNode, EmptyStr, NodeData);
|
||||
NewNode->Text = GetDisplayName(NewNode);
|
||||
NewNode->HasChildren = true;
|
||||
if (GetDriveTypetoNode(ParentNode) != DRIVE_REMOTE)
|
||||
if ((GetDriveTypetoNode(ParentNode) != DRIVE_REMOTE) &&
|
||||
!NodeData->IsRemote)
|
||||
{
|
||||
FSubDirReaderThread->Add(NewNode, IncludeTrailingBackslash(ParentPath) + SRec.Name);
|
||||
FSubDirReaderThread->Add(NewNode, Path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <shlwapi.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <psapi.h>
|
||||
#include <winioctl.h>
|
||||
#include <SessionInfo.h>
|
||||
#include <Soap.EncdDecd.hpp>
|
||||
#pragma clang diagnostic push
|
||||
@@ -1057,6 +1058,7 @@ bool __fastcall IsReservedName(UnicodeString FileName)
|
||||
// Inspired by
|
||||
// https://stackoverflow.com/q/18580945/850848
|
||||
// This can be reimplemented using PathCchCanonicalizeEx on Windows 8 and later
|
||||
// There's also TPath.GetPosAfterExtendedPrefix.
|
||||
enum PATH_PREFIX_TYPE
|
||||
{
|
||||
PPT_UNKNOWN,
|
||||
@@ -1589,6 +1591,73 @@ void __fastcall ProcessLocalDirectory(UnicodeString DirName,
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
// Similar structure might be a part of WINAPI eventually (VCLCOPY - sic)
|
||||
struct REPARSE_DATA_BUFFER
|
||||
{
|
||||
ULONG ReparseTag;
|
||||
USHORT ReparseDataLength;
|
||||
USHORT Reserved;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
USHORT SubstituteNameOffset;
|
||||
USHORT SubstituteNameLength;
|
||||
USHORT PrintNameOffset;
|
||||
USHORT PrintNameLength;
|
||||
ULONG Flags;
|
||||
WCHAR PathBuffer[1];
|
||||
} SymbolicLink;
|
||||
struct
|
||||
{
|
||||
USHORT SubstituteNameOffset;
|
||||
USHORT SubstituteNameLength;
|
||||
USHORT PrintNameOffset;
|
||||
USHORT PrintNameLength;
|
||||
WCHAR PathBuffer[1];
|
||||
} MountPoint;
|
||||
} ReparseBuffer;
|
||||
};
|
||||
//---------------------------------------------------------------------------
|
||||
// Contrary to VCL's FileGetSymLinkTarget, this does not try to open the target folder and does not throw an exception.
|
||||
// Also it would call CreateFile without FILE_FLAG_OPEN_REPARSE_POINT, what also tries to access the remote directory,
|
||||
// potentially stalling. And with FILE_FLAG_OPEN_REPARSE_POINT, the GetFinalPathNameByHandle does not work.
|
||||
bool FileGetSymLinkTargetSafe(const String & FileName, String & Target)
|
||||
{
|
||||
bool Result = false;
|
||||
HANDLE Handle =
|
||||
CreateFile(
|
||||
FileName.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||||
if (Handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD bytesReturned = 0;
|
||||
char Buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
auto Reparse = reinterpret_cast<REPARSE_DATA_BUFFER *>(Buffer);
|
||||
if (DeviceIoControl(Handle, FSCTL_GET_REPARSE_POINT, NULL, 0, Reparse, sizeof(Buffer), &bytesReturned, NULL))
|
||||
{
|
||||
BYTE * NamePtr = nullptr;
|
||||
USHORT NameSize = 0;
|
||||
if (Reparse->ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
||||
{
|
||||
NamePtr = reinterpret_cast<BYTE*>(Reparse->ReparseBuffer.SymbolicLink.PathBuffer) + Reparse->ReparseBuffer.SymbolicLink.PrintNameOffset;
|
||||
NameSize = Reparse->ReparseBuffer.SymbolicLink.PrintNameLength;
|
||||
}
|
||||
else if (Reparse->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
||||
{
|
||||
NamePtr = reinterpret_cast<BYTE*>(Reparse->ReparseBuffer.MountPoint.PathBuffer) + Reparse->ReparseBuffer.MountPoint.PrintNameOffset;
|
||||
NameSize = Reparse->ReparseBuffer.MountPoint.PrintNameLength;
|
||||
}
|
||||
if (NamePtr != nullptr)
|
||||
{
|
||||
Target = String(reinterpret_cast<wchar_t *>(NamePtr), NameSize / sizeof(wchar_t));
|
||||
Result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
int __fastcall FileGetAttrFix(const UnicodeString & FileName)
|
||||
{
|
||||
// Already called with ApiPath
|
||||
@@ -1610,6 +1679,11 @@ int __fastcall FileGetAttrFix(const UnicodeString & FileName)
|
||||
// On Samba, InternalGetFileNameFromSymLink fails and returns true but empty target.
|
||||
// That confuses FileGetAttr, which returns attributes of the parent folder instead.
|
||||
// Using FileGetSymLinkTarget solves the problem, as it returns false.
|
||||
|
||||
// Might want to use FileGetSymLinkTargetSafe here for consistency,
|
||||
// but flaws/limitations of VCL FileGetSymLinkTarget probably does not matter here.
|
||||
// The FileGetSymLinkTarget does not return the target when it cannot open it.
|
||||
// But then GetFileAttributes would fail anyway too, so we return -1 is either case.
|
||||
if (!FileGetSymLinkTarget(FileName, TargetName))
|
||||
{
|
||||
// FileGetAttr would return faInvalid (-1), but we want to allow an upload from Samba,
|
||||
|
||||
@@ -269,6 +269,7 @@ int __fastcall FindNextChecked(TSearchRecChecked & F);
|
||||
int __fastcall FindNextUnchecked(TSearchRecChecked & F);
|
||||
void __fastcall ProcessLocalDirectory(UnicodeString DirName,
|
||||
TProcessLocalFileEvent CallBackFunc, void * Param = NULL, int FindAttrs = -1);
|
||||
bool FileGetSymLinkTargetSafe(const UnicodeString & FileName, UnicodeString & Target);
|
||||
int __fastcall FileGetAttrFix(const UnicodeString & FileName);
|
||||
//---------------------------------------------------------------------------
|
||||
extern const wchar_t * DSTModeNames;
|
||||
|
||||
@@ -80,6 +80,7 @@ type
|
||||
FScanned: Boolean;
|
||||
FData: Pointer;
|
||||
FIsRecycleBin: Boolean;
|
||||
FIsRemote: Boolean;
|
||||
FIconEmpty: Boolean;
|
||||
FSchedule: TSubDirReaderSchedule;
|
||||
|
||||
@@ -95,6 +96,7 @@ type
|
||||
property Scanned: Boolean read FScanned write FScanned;
|
||||
property Data: Pointer read FData write FData;
|
||||
property IsRecycleBin: Boolean read FIsRecycleBin write FIsRecycleBin;
|
||||
property IsRemote: Boolean read FIsRemote write FIsRemote;
|
||||
property IconEmpty: Boolean read FIconEmpty write FIconEmpty;
|
||||
property Schedule: TSubDirReaderSchedule read FSchedule write FSchedule;
|
||||
end;
|
||||
@@ -183,7 +185,7 @@ type
|
||||
procedure ReadSubDirs(Node: TTreeNode); virtual; abstract;
|
||||
procedure CancelDelayedNode(Node: TTreeNode);
|
||||
procedure DelayedNodeTimer(Sender: TObject);
|
||||
function ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer): Boolean;
|
||||
function ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer; Start: TDateTime): Boolean;
|
||||
procedure UpdateDelayedNodeTimer;
|
||||
function DoSearchSubDirs(
|
||||
ParentNode: TTreeNode; Path: string; Level: Integer; ExistingOnly: Boolean;
|
||||
@@ -436,6 +438,7 @@ begin
|
||||
FScanned := False;
|
||||
FDirName := '';
|
||||
FIsRecycleBin := False;
|
||||
FIsRemote := False;
|
||||
FIconEmpty := True;
|
||||
FSchedule := nil;
|
||||
DelayedExclude := nil;
|
||||
@@ -785,6 +788,7 @@ begin
|
||||
NewData.Scanned := SourceData.Scanned;
|
||||
NewData.Data := SourceData.Data;
|
||||
NewData.FIsRecycleBin := SourceData.FIsRecycleBin;
|
||||
NewData.FIsRemote := SourceData.FIsRemote;
|
||||
NewData.IconEmpty := SourceData.IconEmpty;
|
||||
TTreeNode(Source).Data := NewData;
|
||||
end;
|
||||
@@ -1853,15 +1857,13 @@ begin
|
||||
Result := (FindNext(SRec) = 0) and GetSubDir(SRec);
|
||||
end;
|
||||
|
||||
function TDriveViewInt.ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer): Boolean;
|
||||
function TDriveViewInt.ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer; Start: TDateTime): Boolean;
|
||||
var
|
||||
Start: TDateTime;
|
||||
Cont: Boolean;
|
||||
Path: string;
|
||||
Count: Integer;
|
||||
DelayedExclude: TStringList;
|
||||
begin
|
||||
Start := Now;
|
||||
Path := NodePath(Node);
|
||||
Result := True;
|
||||
Count := 0;
|
||||
@@ -1916,7 +1918,7 @@ begin
|
||||
else
|
||||
begin
|
||||
NodeData := TNodeData(Node.Data);
|
||||
if ReadSubDirsBatch(Node, NodeData.DelayedSrec, 10, 50) then
|
||||
if ReadSubDirsBatch(Node, NodeData.DelayedSrec, 10, 50, Now) then
|
||||
begin
|
||||
FreeAndNil(NodeData.DelayedExclude);
|
||||
FDelayedNodes.Delete(0);
|
||||
|
||||
Reference in New Issue
Block a user