1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-28 15:35:08 +03:00
Files
winscp/source/core/Terminal.cpp
Martin Prikryl 020fb1aaab Enforcing binary transfer mode, when ascii is not supported (particularly when encrypting files)
Source commit: 88ff34c5891aa5e0f2ce5e773ffa2a4f46f13d7f
2018-06-21 03:12:25 +02:00

7942 lines
245 KiB
C++

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Terminal.h"
#include <SysUtils.hpp>
#include <FileCtrl.hpp>
#include <StrUtils.hpp>
#include <System.IOUtils.hpp>
#include "Common.h"
#include "PuttyTools.h"
#include "FileBuffer.h"
#include "Interface.h"
#include "RemoteFiles.h"
#include "SecureShell.h"
#include "ScpFileSystem.h"
#include "SftpFileSystem.h"
#include "FtpFileSystem.h"
#include "WebDAVFileSystem.h"
#include "S3FileSystem.h"
#include "TextsCore.h"
#include "HelpCore.h"
#include "CoreMain.h"
#include "Queue.h"
#include "Cryptography.h"
#include <openssl/pkcs12.h>
#include <openssl/err.h>
#ifndef AUTO_WINSOCK
#include <winsock2.h>
#endif
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
#define FILE_OPERATION_LOOP_TERMINAL this
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class TLoopDetector
{
public:
__fastcall TLoopDetector();
void __fastcall RecordVisitedDirectory(const UnicodeString & Directory);
bool __fastcall IsUnvisitedDirectory(const UnicodeString & Directory);
private:
std::unique_ptr<TStringList> FVisitedDirectories;
};
//---------------------------------------------------------------------------
__fastcall TLoopDetector::TLoopDetector()
{
FVisitedDirectories.reset(CreateSortedStringList());
}
//---------------------------------------------------------------------------
void __fastcall TLoopDetector::RecordVisitedDirectory(const UnicodeString & Directory)
{
UnicodeString VisitedDirectory = ExcludeTrailingBackslash(Directory);
FVisitedDirectories->Add(VisitedDirectory);
}
//---------------------------------------------------------------------------
bool __fastcall TLoopDetector::IsUnvisitedDirectory(const UnicodeString & Directory)
{
bool Result = (FVisitedDirectories->IndexOf(Directory) < 0);
if (Result)
{
RecordVisitedDirectory(Directory);
}
return Result;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
struct TMoveFileParams
{
UnicodeString Target;
UnicodeString FileMask;
};
//---------------------------------------------------------------------------
struct TFilesFindParams
{
TFileMasks FileMask;
TFileFoundEvent OnFileFound;
TFindingFileEvent OnFindingFile;
bool Cancel;
TLoopDetector LoopDetector;
UnicodeString RealDirectory;
};
//---------------------------------------------------------------------------
TCalculateSizeStats::TCalculateSizeStats()
{
memset(this, 0, sizeof(*this));
}
//---------------------------------------------------------------------------
TSynchronizeOptions::TSynchronizeOptions()
{
memset(this, 0, sizeof(*this));
}
//---------------------------------------------------------------------------
TSynchronizeOptions::~TSynchronizeOptions()
{
delete Filter;
}
//---------------------------------------------------------------------------
bool __fastcall TSynchronizeOptions::MatchesFilter(const UnicodeString & FileName)
{
int FoundIndex;
bool Result;
if (Filter == NULL)
{
Result = true;
}
else
{
Result = Filter->Find(FileName, FoundIndex);
}
return Result;
}
//---------------------------------------------------------------------------
TSpaceAvailable::TSpaceAvailable()
{
memset(this, 0, sizeof(*this));
}
//---------------------------------------------------------------------------
TOverwriteFileParams::TOverwriteFileParams()
{
SourceSize = 0;
DestSize = 0;
SourcePrecision = mfFull;
DestPrecision = mfFull;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TSynchronizeChecklist::TItem::TItem() :
Action(saNone), IsDirectory(false), RemoteFile(NULL), Checked(true), ImageIndex(-1)
{
Local.ModificationFmt = mfFull;
Local.Modification = 0;
Local.Size = 0;
Remote.ModificationFmt = mfFull;
Remote.Modification = 0;
Remote.Size = 0;
}
//---------------------------------------------------------------------------
TSynchronizeChecklist::TItem::~TItem()
{
delete RemoteFile;
}
//---------------------------------------------------------------------------
const UnicodeString& TSynchronizeChecklist::TItem::GetFileName() const
{
if (!Remote.FileName.IsEmpty())
{
return Remote.FileName;
}
else
{
DebugAssert(!Local.FileName.IsEmpty());
return Local.FileName;
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TSynchronizeChecklist::TSynchronizeChecklist() :
FList(new TList())
{
}
//---------------------------------------------------------------------------
TSynchronizeChecklist::~TSynchronizeChecklist()
{
for (int Index = 0; Index < FList->Count; Index++)
{
delete static_cast<TItem *>(FList->Items[Index]);
}
delete FList;
}
//---------------------------------------------------------------------------
void TSynchronizeChecklist::Add(TItem * Item)
{
FList->Add(Item);
}
//---------------------------------------------------------------------------
int __fastcall TSynchronizeChecklist::Compare(void * AItem1, void * AItem2)
{
TItem * Item1 = static_cast<TItem *>(AItem1);
TItem * Item2 = static_cast<TItem *>(AItem2);
int Result;
if (!Item1->Local.Directory.IsEmpty())
{
Result = AnsiCompareText(Item1->Local.Directory, Item2->Local.Directory);
}
else
{
DebugAssert(!Item1->Remote.Directory.IsEmpty());
Result = AnsiCompareText(Item1->Remote.Directory, Item2->Remote.Directory);
}
if (Result == 0)
{
Result = AnsiCompareText(Item1->GetFileName(), Item2->GetFileName());
}
return Result;
}
//---------------------------------------------------------------------------
void TSynchronizeChecklist::Sort()
{
FList->Sort(Compare);
}
//---------------------------------------------------------------------------
int TSynchronizeChecklist::GetCount() const
{
return FList->Count;
}
//---------------------------------------------------------------------------
const TSynchronizeChecklist::TItem * TSynchronizeChecklist::GetItem(int Index) const
{
return static_cast<TItem *>(FList->Items[Index]);
}
//---------------------------------------------------------------------------
void __fastcall TSynchronizeChecklist::Update(const TItem * Item, bool Check, TAction Action)
{
// TSynchronizeChecklist owns non-const items so it can manipulate them freely,
// const_cast here is just an optimization
TItem * MutableItem = const_cast<TItem *>(Item);
DebugAssert(FList->IndexOf(MutableItem) >= 0);
MutableItem->Checked = Check;
MutableItem->Action = Action;
}
//---------------------------------------------------------------------------
TSynchronizeChecklist::TAction __fastcall TSynchronizeChecklist::Reverse(TSynchronizeChecklist::TAction Action)
{
switch (Action)
{
case saUploadNew:
return saDeleteLocal;
case saDownloadNew:
return saDeleteRemote;
case saUploadUpdate:
return saDownloadUpdate;
case saDownloadUpdate:
return saUploadUpdate;
case saDeleteRemote:
return saDownloadNew;
case saDeleteLocal:
return saUploadNew;
default:
case saNone:
DebugFail();
return saNone;
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class TTunnelThread : public TSimpleThread
{
public:
__fastcall TTunnelThread(TSecureShell * SecureShell);
virtual __fastcall ~TTunnelThread();
virtual void __fastcall Terminate();
protected:
virtual void __fastcall Execute();
private:
TSecureShell * FSecureShell;
bool FTerminated;
};
//---------------------------------------------------------------------------
__fastcall TTunnelThread::TTunnelThread(TSecureShell * SecureShell) :
FSecureShell(SecureShell),
FTerminated(false)
{
Start();
}
//---------------------------------------------------------------------------
__fastcall TTunnelThread::~TTunnelThread()
{
// close before the class's virtual functions (Terminate particularly) are lost
Close();
}
//---------------------------------------------------------------------------
void __fastcall TTunnelThread::Terminate()
{
FTerminated = true;
}
//---------------------------------------------------------------------------
void __fastcall TTunnelThread::Execute()
{
try
{
while (!FTerminated)
{
FSecureShell->Idle(250);
}
}
catch(...)
{
if (FSecureShell->Active)
{
FSecureShell->Close();
}
// do not pass exception out of thread's proc
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class TTunnelUI : public TSessionUI
{
public:
__fastcall TTunnelUI(TTerminal * Terminal);
virtual void __fastcall Information(const UnicodeString & Str, bool Status);
virtual unsigned int __fastcall QueryUser(const UnicodeString Query,
TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
TQueryType QueryType);
virtual unsigned int __fastcall QueryUserException(const UnicodeString Query,
Exception * E, unsigned int Answers, const TQueryParams * Params,
TQueryType QueryType);
virtual bool __fastcall PromptUser(TSessionData * Data, TPromptKind Kind,
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts,
TStrings * Results);
virtual void __fastcall DisplayBanner(const UnicodeString & Banner);
virtual void __fastcall FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpContext);
virtual void __fastcall HandleExtendedException(Exception * E);
virtual void __fastcall Closed();
virtual void __fastcall ProcessGUI();
private:
TTerminal * FTerminal;
unsigned int FTerminalThread;
};
//---------------------------------------------------------------------------
__fastcall TTunnelUI::TTunnelUI(TTerminal * Terminal)
{
FTerminal = Terminal;
FTerminalThread = GetCurrentThreadId();
}
//---------------------------------------------------------------------------
void __fastcall TTunnelUI::Information(const UnicodeString & Str, bool Status)
{
if (GetCurrentThreadId() == FTerminalThread)
{
FTerminal->Information(Str, Status);
}
}
//---------------------------------------------------------------------------
unsigned int __fastcall TTunnelUI::QueryUser(const UnicodeString Query,
TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
TQueryType QueryType)
{
unsigned int Result;
if (GetCurrentThreadId() == FTerminalThread)
{
Result = FTerminal->QueryUser(Query, MoreMessages, Answers, Params, QueryType);
}
else
{
Result = AbortAnswer(Answers);
}
return Result;
}
//---------------------------------------------------------------------------
unsigned int __fastcall TTunnelUI::QueryUserException(const UnicodeString Query,
Exception * E, unsigned int Answers, const TQueryParams * Params,
TQueryType QueryType)
{
unsigned int Result;
if (GetCurrentThreadId() == FTerminalThread)
{
Result = FTerminal->QueryUserException(Query, E, Answers, Params, QueryType);
}
else
{
Result = AbortAnswer(Answers);
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTunnelUI::PromptUser(TSessionData * Data, TPromptKind Kind,
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts, TStrings * Results)
{
bool Result;
if (GetCurrentThreadId() == FTerminalThread)
{
if (IsAuthenticationPrompt(Kind))
{
Instructions =
FMTLOAD(TUNNEL_INSTRUCTION2, (Data->HostName)) +
(Instructions.IsEmpty() ? L"" : L"\n") +
Instructions;
}
Result = FTerminal->PromptUser(Data, Kind, Name, Instructions, Prompts, Results);
}
else
{
Result = false;
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTunnelUI::DisplayBanner(const UnicodeString & Banner)
{
if (GetCurrentThreadId() == FTerminalThread)
{
FTerminal->DisplayBanner(Banner);
}
}
//---------------------------------------------------------------------------
void __fastcall TTunnelUI::FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpKeyword)
{
throw ESshFatal(E, Msg, HelpKeyword);
}
//---------------------------------------------------------------------------
void __fastcall TTunnelUI::HandleExtendedException(Exception * E)
{
if (GetCurrentThreadId() == FTerminalThread)
{
FTerminal->HandleExtendedException(E);
}
}
//---------------------------------------------------------------------------
void __fastcall TTunnelUI::Closed()
{
// noop
}
//---------------------------------------------------------------------------
void __fastcall TTunnelUI::ProcessGUI()
{
// noop
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class TCallbackGuard
{
public:
inline __fastcall TCallbackGuard(TTerminal * FTerminal);
inline __fastcall ~TCallbackGuard();
void __fastcall FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword);
inline void __fastcall Verify();
inline bool __fastcall Verify(Exception * E);
void __fastcall Dismiss();
private:
ExtException * FFatalError;
TTerminal * FTerminal;
bool FGuarding;
};
//---------------------------------------------------------------------------
__fastcall TCallbackGuard::TCallbackGuard(TTerminal * Terminal) :
FTerminal(Terminal),
FFatalError(NULL),
FGuarding(FTerminal->FCallbackGuard == NULL)
{
if (FGuarding)
{
FTerminal->FCallbackGuard = this;
}
}
//---------------------------------------------------------------------------
__fastcall TCallbackGuard::~TCallbackGuard()
{
if (FGuarding)
{
DebugAssert((FTerminal->FCallbackGuard == this) || (FTerminal->FCallbackGuard == NULL));
FTerminal->FCallbackGuard = NULL;
}
delete FFatalError;
}
//---------------------------------------------------------------------------
void __fastcall TCallbackGuard::FatalError(Exception * E, const UnicodeString & Msg, const UnicodeString & HelpKeyword)
{
DebugAssert(FGuarding);
// make sure we do not bother about getting back the silent abort exception
// we issued ourselves. this may happen when there is an exception handler
// that converts any exception to fatal one (such as in TTerminal::Open).
if (dynamic_cast<ECallbackGuardAbort *>(E) == NULL)
{
delete FFatalError;
FFatalError = new ExtException(E, Msg, HelpKeyword);
}
// silently abort what we are doing.
// non-silent exception would be caught probably by default application
// exception handler, which may not do an appropriate action
// (particularly it will not resume broken transfer).
throw ECallbackGuardAbort();
}
//---------------------------------------------------------------------------
void __fastcall TCallbackGuard::Dismiss()
{
DebugAssert(FFatalError == NULL);
FGuarding = false;
}
//---------------------------------------------------------------------------
void __fastcall TCallbackGuard::Verify()
{
if (FGuarding)
{
FGuarding = false;
DebugAssert(FTerminal->FCallbackGuard == this);
FTerminal->FCallbackGuard = NULL;
if (FFatalError != NULL)
{
throw ESshFatal(FFatalError, L"");
}
}
}
//---------------------------------------------------------------------------
bool __fastcall TCallbackGuard::Verify(Exception * E)
{
bool Result =
(dynamic_cast<ECallbackGuardAbort *>(E) != NULL);
if (Result)
{
DebugAssert(FGuarding && (FFatalError != NULL));
Verify();
}
return Result;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TRobustOperationLoop::TRobustOperationLoop(TTerminal * Terminal, TFileOperationProgressType * OperationProgress, bool * AnyTransfer) :
FTerminal(Terminal),
FOperationProgress(OperationProgress),
FRetry(false),
FAnyTransfer(AnyTransfer)
{
if (FAnyTransfer != NULL)
{
FPrevAnyTransfer = *FAnyTransfer;
*FAnyTransfer = false;
FStart = Now();
}
}
//---------------------------------------------------------------------------
TRobustOperationLoop::~TRobustOperationLoop()
{
if (FAnyTransfer != NULL)
{
*FAnyTransfer = FPrevAnyTransfer;
}
}
//---------------------------------------------------------------------------
bool TRobustOperationLoop::TryReopen(Exception & E)
{
FRetry = !FTerminal->Active;
if (FRetry)
{
if (FAnyTransfer != NULL)
{
// if there was any transfer, reset the global timestamp
if (*FAnyTransfer)
{
FStart = Now();
FPrevAnyTransfer = true;
*FAnyTransfer = false;
}
else
{
FRetry = FTerminal->ContinueReopen(FStart);
}
}
if (FRetry)
{
FRetry =
FTerminal->QueryReopen(&E, ropNoReadDirectory, FOperationProgress);
}
}
return FRetry;
}
//---------------------------------------------------------------------------
bool TRobustOperationLoop::ShouldRetry()
{
return FRetry;
}
//---------------------------------------------------------------------------
bool TRobustOperationLoop::Retry()
{
bool Result = FRetry;
FRetry = false;
return Result;
}
//---------------------------------------------------------------------------
class TRetryOperationLoop
{
public:
TRetryOperationLoop(TTerminal * Terminal);
void Error(Exception & E);
void Error(Exception & E, TSessionAction & Action);
void Error(Exception & E, const UnicodeString & Message);
void Error(Exception & E, TSessionAction & Action, const UnicodeString & Message);
bool Retry();
private:
TTerminal * FTerminal;
bool FRetry;
void DoError(Exception & E, TSessionAction * Action, const UnicodeString & Message);
};
//---------------------------------------------------------------------------
TRetryOperationLoop::TRetryOperationLoop(TTerminal * Terminal)
{
FTerminal = Terminal;
FRetry = false;
}
//---------------------------------------------------------------------------
void TRetryOperationLoop::DoError(Exception & E, TSessionAction * Action, const UnicodeString & Message)
{
// Note that the action may already be canceled when RollbackAction is called
unsigned int Result;
try
{
Result = FTerminal->CommandError(&E, Message, qaRetry | qaSkip | qaAbort);
}
catch (Exception & E2)
{
if (Action != NULL)
{
FTerminal->RollbackAction(*Action, NULL, &E2);
}
throw;
}
switch (Result)
{
case qaRetry:
FRetry = true;
if (Action != NULL)
{
Action->Cancel();
}
break;
case qaAbort:
if (Action != NULL)
{
FTerminal->RollbackAction(*Action, NULL, &E);
}
Abort();
break;
case qaSkip:
if (Action != NULL)
{
Action->Cancel();
}
break;
default:
DebugFail();
break;
}
}
//---------------------------------------------------------------------------
void TRetryOperationLoop::Error(Exception & E)
{
DoError(E, NULL, UnicodeString());
}
//---------------------------------------------------------------------------
void TRetryOperationLoop::Error(Exception & E, TSessionAction & Action)
{
DoError(E, &Action, UnicodeString());
}
//---------------------------------------------------------------------------
void TRetryOperationLoop::Error(Exception & E, const UnicodeString & Message)
{
DoError(E, NULL, Message);
}
//---------------------------------------------------------------------------
void TRetryOperationLoop::Error(Exception & E, TSessionAction & Action, const UnicodeString & Message)
{
DoError(E, &Action, Message);
}
//---------------------------------------------------------------------------
bool TRetryOperationLoop::Retry()
{
bool Result = FRetry;
FRetry = false;
return Result;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TCollectedFileList::TCollectedFileList()
{
}
//---------------------------------------------------------------------------
int TCollectedFileList::Add(const UnicodeString & FileName, TObject * Object, bool Dir)
{
TFileData Data;
Data.FileName = FileName;
Data.Object = Object;
Data.Dir = Dir;
Data.Recursed = true;
FList.push_back(Data);
return Count() - 1;
}
//---------------------------------------------------------------------------
void TCollectedFileList::DidNotRecurse(int Index)
{
FList[Index].Recursed = false;
}
//---------------------------------------------------------------------------
void TCollectedFileList::Delete(int Index)
{
FList.erase(FList.begin() + Index);
}
//---------------------------------------------------------------------------
int TCollectedFileList::Count() const
{
return FList.size();
}
//---------------------------------------------------------------------------
UnicodeString TCollectedFileList::GetFileName(int Index) const
{
return FList[Index].FileName;
}
//---------------------------------------------------------------------------
TObject * TCollectedFileList::GetObject(int Index) const
{
return FList[Index].Object;
}
//---------------------------------------------------------------------------
bool TCollectedFileList::IsDir(int Index) const
{
return FList[Index].Dir;
}
//---------------------------------------------------------------------------
bool TCollectedFileList::IsRecursed(int Index) const
{
return FList[Index].Recursed;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TParallelOperation::TParallelOperation(TOperationSide Side)
{
FCopyParam = NULL;
FParams = 0;
FProbablyEmpty = false;
FClients = 0;
FMainOperationProgress = NULL;
DebugAssert((Side == osLocal) || (Side == osRemote));
FSide = Side;
}
//---------------------------------------------------------------------------
void TParallelOperation::Init(
TStrings * AFileList, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
TFileOperationProgressType * MainOperationProgress, const UnicodeString & MainName)
{
DebugAssert(FFileList.get() == NULL);
// More lists should really happen in scripting only, which does not support parallel transfers atm.
// But in general the code should work with more lists anyway, it just was not tested for it.
DebugAssert(AFileList->Count == 1);
FFileList.reset(AFileList);
FSection.reset(new TCriticalSection());
FTargetDir = TargetDir;
FCopyParam = CopyParam;
FParams = Params;
FMainOperationProgress = MainOperationProgress;
FMainName = MainName;
FIndex = 0;
}
//---------------------------------------------------------------------------
TParallelOperation::~TParallelOperation()
{
WaitFor();
}
//---------------------------------------------------------------------------
bool TParallelOperation::IsInitialized()
{
return (FMainOperationProgress != NULL);
}
//---------------------------------------------------------------------------
bool TParallelOperation::ShouldAddClient()
{
bool Result;
// TTransferQueueItem::ProgressUpdated() already checks IsInitialized() before calling this
if (!IsInitialized())
{
Result = false;
}
else
{
TGuard Guard(FSection.get());
Result = !FProbablyEmpty && (FMainOperationProgress->Cancel < csCancel);
}
return Result;
}
//---------------------------------------------------------------------------
void TParallelOperation::AddClient()
{
TGuard Guard(FSection.get());
FClients++;
}
//---------------------------------------------------------------------------
void TParallelOperation::RemoveClient()
{
TGuard Guard(FSection.get());
FClients--;
}
//---------------------------------------------------------------------------
void TParallelOperation::WaitFor()
{
// Even initialized?
// Won't be, when parallel transfers were not possible (like when preserving of directory timestamps is enabled)
if (FSection.get() != NULL)
{
bool Done;
do
{
{
TGuard Guard(FSection.get());
Done = (FClients == 0);
}
if (!Done)
{
// propagate the total progress incremented by the parallel operations
FMainOperationProgress->Progress();
Sleep(200);
}
}
while (!Done);
FProbablyEmpty = true;
}
else
{
DebugAssert(FClients == 0);
}
}
//---------------------------------------------------------------------------
void TParallelOperation::Done(const UnicodeString & FileName, bool Dir, bool Success)
{
if (Dir)
{
TGuard Guard(FSection.get());
TDirectories::iterator DirectoryIterator = FDirectories.find(FileName);
if (DebugAlwaysTrue(DirectoryIterator != FDirectories.end()))
{
if (Success)
{
DebugAssert(!DirectoryIterator->second.Exists);
DirectoryIterator->second.Exists = true;
}
else
{
// This is actually not useful at the moment, as when creating directory fails and "Skip" is pressed,
// the current code in CopyToRemote/CreateDirectory will behave as, if it succedded, so Successs will be true here.
FDirectories.erase(DirectoryIterator);
if (FFileList->Count > 0)
{
UnicodeString FileNameWithSlash;
if (FSide == osLocal)
{
FileNameWithSlash = IncludeTrailingBackslash(FileName);
}
else
{
FileNameWithSlash = UnixIncludeTrailingBackslash(FileName);
}
// It can actually be a different list than the one the directory was taken from,
// but that does not matter that much. It should not happen anyway, as more lists should be in scripting only.
TCollectedFileList * Files = DebugNotNull(dynamic_cast<TCollectedFileList *>(FFileList->Objects[0]));
int Index = 0;
while (Index < Files->Count())
{
if (StartsText(FileNameWithSlash, Files->GetFileName(Index)))
{
// We should add the file to "skip" counters in the OperationProgress,
// but an interactive foreground transfer is not doing that either yet.
Files->Delete(Index);
if (Index < FIndex)
{
FIndex--;
}
}
else
{
Index++;
}
}
}
}
}
}
}
//---------------------------------------------------------------------------
bool TParallelOperation::CheckEnd(TCollectedFileList * Files)
{
bool Result = (FIndex >= Files->Count());
if (Result)
{
FFileList->Delete(0);
FIndex = 0;
}
return Result;
}
//---------------------------------------------------------------------------
int TParallelOperation::GetNext(TTerminal * Terminal, UnicodeString & FileName, TObject *& Object, UnicodeString & TargetDir, bool & Dir, bool & Recursed)
{
TGuard Guard(FSection.get());
int Result = 1;
TCollectedFileList * Files;
do
{
if (FFileList->Count > 0)
{
Files = DebugNotNull(dynamic_cast<TCollectedFileList *>(FFileList->Objects[0]));
// can happen if the file was excluded by file mask
if (CheckEnd(Files))
{
Files = NULL;
}
}
else
{
Files = NULL;
Result = -1;
}
}
while ((Result == 1) && (Files == NULL));
if (Files != NULL)
{
UnicodeString RootPath = FFileList->Strings[0];
FileName = Files->GetFileName(FIndex);
Object = Files->GetObject(FIndex);
Dir = Files->IsDir(FIndex);
Recursed = Files->IsRecursed(FIndex);
UnicodeString DirPath;
bool FirstLevel;
if (FSide == osLocal)
{
DirPath = ExtractFileDir(FileName);
FirstLevel = SamePaths(DirPath, RootPath);
}
else
{
DirPath = UnixExtractFileDir(FileName);
FirstLevel = UnixSamePath(DirPath, RootPath);
}
if (FirstLevel)
{
TargetDir = FTargetDir;
}
else
{
TDirectories::const_iterator DirectoryIterator = FDirectories.find(DirPath);
if (DebugAlwaysFalse(DirectoryIterator == FDirectories.end()))
{
throw EInvalidOperation(L"Parent path not known");
}
const TDirectoryData & DirectoryData = DirectoryIterator->second;
if (!DirectoryData.Exists)
{
Result = 0; // wait for parent directory to be created
}
else
{
TargetDir = DirectoryData.OppositePath;
}
}
if (!TargetDir.IsEmpty())
{
if (Dir)
{
TDirectoryData DirectoryData;
UnicodeString OnlyFileName;
if (FSide == osLocal)
{
OnlyFileName = ExtractFileName(FileName);
}
else
{
OnlyFileName = UnixExtractFileName(FileName);
}
OnlyFileName = Terminal->ChangeFileName(FCopyParam, OnlyFileName, osRemote, FirstLevel);
if (FSide == osLocal)
{
DirectoryData.OppositePath = UnixCombinePaths(TargetDir, OnlyFileName);
}
else
{
DirectoryData.OppositePath = TPath::Combine(TargetDir, OnlyFileName);
}
DirectoryData.Exists = false;
FDirectories.insert(std::make_pair(FileName, DirectoryData));
}
FIndex++;
CheckEnd(Files);
}
}
FProbablyEmpty = (FFileList->Count == 0);
return Result;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
__fastcall TTerminal::TTerminal(TSessionData * SessionData,
TConfiguration * Configuration)
{
FConfiguration = Configuration;
FSessionData = new TSessionData(L"");
FSessionData->Assign(SessionData);
TDateTime Started = Now(); // use the same time for session and XML log
FLog = new TSessionLog(this, Started, FSessionData, Configuration);
FActionLog = new TActionLog(this, Started, FSessionData, Configuration);
FFiles = new TRemoteDirectory(this);
FExceptionOnFail = 0;
FInTransaction = 0;
FReadCurrentDirectoryPending = false;
FReadDirectoryPending = false;
FUsersGroupsLookedup = False;
FTunnelLocalPortNumber = 0;
FFileSystem = NULL;
FSecureShell = NULL;
FOnProgress = NULL;
FOnFinished = NULL;
FOnDeleteLocalFile = NULL;
FOnReadDirectoryProgress = NULL;
FOnQueryUser = NULL;
FOnPromptUser = NULL;
FOnDisplayBanner = NULL;
FOnShowExtendedException = NULL;
FOnInformation = NULL;
FOnCustomCommand = NULL;
FOnClose = NULL;
FOnFindingFile = NULL;
FUseBusyCursor = True;
FLockDirectory = L"";
FDirectoryCache = new TRemoteDirectoryCache();
FDirectoryChangesCache = NULL;
FFSProtocol = cfsUnknown;
FCommandSession = NULL;
FAutoReadDirectory = true;
FReadingCurrentDirectory = false;
FStatus = ssClosed;
FOpening = 0;
FTunnelThread = NULL;
FTunnel = NULL;
FTunnelData = NULL;
FTunnelLog = NULL;
FTunnelUI = NULL;
FTunnelOpening = false;
FCallbackGuard = NULL;
FNesting = 0;
}
//---------------------------------------------------------------------------
__fastcall TTerminal::~TTerminal()
{
if (Active)
{
Close();
}
if (FCallbackGuard != NULL)
{
// see TTerminal::HandleExtendedException
FCallbackGuard->Dismiss();
}
DebugAssert(FTunnel == NULL);
SAFE_DESTROY(FCommandSession);
if (SessionData->CacheDirectoryChanges && SessionData->PreserveDirectoryChanges &&
(FDirectoryChangesCache != NULL))
{
Configuration->SaveDirectoryChangesCache(SessionData->SessionKey,
FDirectoryChangesCache);
}
SAFE_DESTROY_EX(TCustomFileSystem, FFileSystem);
SAFE_DESTROY_EX(TSessionLog, FLog);
SAFE_DESTROY_EX(TActionLog, FActionLog);
delete FFiles;
delete FDirectoryCache;
delete FDirectoryChangesCache;
SAFE_DESTROY(FSessionData);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Idle()
{
// Once we disconnect, do nothing, until reconnect handler
// "receives the information".
// Never go idle when called from within ::ProcessGUI() call
// as we may recurse for good, timeouting eventually.
if (Active && (FNesting == 0))
{
TAutoNestingCounter NestingCounter(FNesting);
if (Configuration->ActualLogProtocol >= 1)
{
LogEvent(L"Session upkeep");
}
DebugAssert(FFileSystem != NULL);
FFileSystem->Idle();
if (CommandSessionOpened)
{
try
{
FCommandSession->Idle();
}
catch(Exception & E)
{
// If the secondary session is dropped, ignore the error and let
// it be reconnected when needed.
// BTW, non-fatal error can hardly happen here, that's why
// it is displayed, because it can be useful to know.
if (FCommandSession->Active)
{
FCommandSession->HandleExtendedException(&E);
}
}
}
}
}
//---------------------------------------------------------------------
RawByteString __fastcall TTerminal::EncryptPassword(const UnicodeString & Password)
{
return Configuration->EncryptPassword(Password, SessionData->SessionName);
}
//---------------------------------------------------------------------
UnicodeString __fastcall TTerminal::DecryptPassword(const RawByteString & Password)
{
UnicodeString Result;
try
{
Result = Configuration->DecryptPassword(Password, SessionData->SessionName);
}
catch(EAbort &)
{
// silently ignore aborted prompts for master password and return empty password
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::RecryptPasswords()
{
FSessionData->RecryptPasswords();
FRememberedPassword = EncryptPassword(DecryptPassword(FRememberedPassword));
FRememberedTunnelPassword = EncryptPassword(DecryptPassword(FRememberedTunnelPassword));
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::ExpandFileName(UnicodeString Path,
const UnicodeString BasePath)
{
// replace this by AbsolutePath()
Path = UnixExcludeTrailingBackslash(Path);
if (!UnixIsAbsolutePath(Path) && !BasePath.IsEmpty())
{
// TODO: Handle more complicated cases like "../../xxx"
if (Path == L"..")
{
Path = UnixExcludeTrailingBackslash(UnixExtractFilePath(
UnixExcludeTrailingBackslash(BasePath)));
}
else
{
Path = UnixIncludeTrailingBackslash(BasePath) + Path;
}
}
return Path;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::GetActive()
{
return (FFileSystem != NULL) && FFileSystem->GetActive();
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Close()
{
FFileSystem->Close();
// Cannot rely on CommandSessionOpened here as Status is set to ssClosed too late
if ((FCommandSession != NULL) && FCommandSession->Active)
{
// prevent recursion
FCommandSession->OnClose = NULL;
FCommandSession->Close();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ResetConnection()
{
// used to be called from Reopen(), why?
FTunnelError = L"";
FRememberedPasswordTried = false;
FRememberedTunnelPasswordTried = false;
if (FDirectoryChangesCache != NULL)
{
delete FDirectoryChangesCache;
FDirectoryChangesCache = NULL;
}
FFiles->Directory = L"";
// note that we cannot clear contained files
// as they can still be referenced in the GUI atm
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FingerprintScan(UnicodeString & SHA256, UnicodeString & MD5)
{
SessionData->FingerprintScan = true;
try
{
Open();
// we should never get here
DebugFail();
Abort();
}
catch (...)
{
if (!FFingerprintScannedSHA256.IsEmpty() || !FFingerprintScannedMD5.IsEmpty())
{
SHA256 = FFingerprintScannedSHA256;
MD5 = FFingerprintScannedMD5;
}
else
{
throw;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Open()
{
TAutoNestingCounter OpeningCounter(FOpening);
ReflectSettings();
try
{
FEncryptKey = HexToBytes(FSessionData->EncryptKey);
DoInformation(L"", true, 1);
try
{
try
{
ResetConnection();
FStatus = ssOpening;
try
{
if (FFileSystem == NULL)
{
Log->AddSystemInfo();
DoInitializeLog();
Log->AddStartupInfo();
}
DebugAssert(FTunnel == NULL);
if (FSessionData->Tunnel)
{
DoInformation(LoadStr(OPEN_TUNNEL), true);
LogEvent(L"Opening tunnel.");
OpenTunnel();
Log->AddSeparator();
FSessionData->ConfigureTunnel(FTunnelLocalPortNumber);
DoInformation(LoadStr(USING_TUNNEL), false);
LogEvent(FORMAT(L"Connecting via tunnel interface %s:%d.",
(FSessionData->HostNameExpanded, FSessionData->PortNumber)));
}
else
{
DebugAssert(FTunnelLocalPortNumber == 0);
}
if (FFileSystem == NULL)
{
if (SessionData->FSProtocol == fsFTP)
{
FFSProtocol = cfsFTP;
FFileSystem = new TFTPFileSystem(this);
FFileSystem->Open();
Log->AddSeparator();
LogEvent(L"Using FTP protocol.");
}
else if (SessionData->FSProtocol == fsWebDAV)
{
FFSProtocol = cfsWebDAV;
FFileSystem = new TWebDAVFileSystem(this);
FFileSystem->Open();
Log->AddSeparator();
LogEvent(L"Using WebDAV protocol.");
}
else if (SessionData->FSProtocol == fsS3)
{
FFSProtocol = cfsS3;
FFileSystem = new TS3FileSystem(this);
FFileSystem->Open();
Log->AddSeparator();
LogEvent(L"Using S3 protocol.");
}
else
{
DebugAssert(FSecureShell == NULL);
try
{
FSecureShell = new TSecureShell(this, FSessionData, Log, Configuration);
try
{
// there will be only one channel in this session
FSecureShell->Simple = true;
FSecureShell->Open();
}
catch(Exception & E)
{
DebugAssert(!FSecureShell->Active);
if (SessionData->FingerprintScan)
{
FSecureShell->GetHostKeyFingerprint(FFingerprintScannedSHA256, FFingerprintScannedMD5);
}
if (!FSecureShell->Active && !FTunnelError.IsEmpty())
{
// the only case where we expect this to happen
DebugAssert(E.Message == LoadStr(UNEXPECTED_CLOSE_ERROR));
FatalError(&E, FMTLOAD(TUNNEL_ERROR, (FTunnelError)));
}
else
{
throw;
}
}
Log->AddSeparator();
if ((SessionData->FSProtocol == fsSCPonly) ||
(SessionData->FSProtocol == fsSFTP && FSecureShell->SshFallbackCmd()))
{
FFSProtocol = cfsSCP;
FFileSystem = new TSCPFileSystem(this, FSecureShell);
FSecureShell = NULL; // ownership passed
LogEvent(L"Using SCP protocol.");
}
else
{
FFSProtocol = cfsSFTP;
FFileSystem = new TSFTPFileSystem(this, FSecureShell);
FSecureShell = NULL; // ownership passed
LogEvent(L"Using SFTP protocol.");
}
}
__finally
{
delete FSecureShell;
FSecureShell = NULL;
}
}
}
else
{
FFileSystem->Open();
}
}
__finally
{
if (FSessionData->Tunnel)
{
FSessionData->RollbackTunnel();
}
}
if (SessionData->CacheDirectoryChanges)
{
DebugAssert(FDirectoryChangesCache == NULL);
FDirectoryChangesCache = new TRemoteDirectoryChangesCache(
Configuration->CacheDirectoryChangesMaxSize);
if (SessionData->PreserveDirectoryChanges)
{
Configuration->LoadDirectoryChangesCache(SessionData->SessionKey,
FDirectoryChangesCache);
}
}
DoStartup();
if (FCollectFileSystemUsage)
{
FFileSystem->CollectUsage();
FCollectFileSystemUsage = false;
}
DoInformation(LoadStr(STATUS_READY), true);
FStatus = ssOpened;
}
catch(...)
{
// rollback
if (FDirectoryChangesCache != NULL)
{
delete FDirectoryChangesCache;
FDirectoryChangesCache = NULL;
}
if (SessionData->FingerprintScan && (FFileSystem != NULL) &&
DebugAlwaysTrue(SessionData->Ftps != ftpsNone))
{
FFingerprintScannedSHA256 = UnicodeString();
FFingerprintScannedMD5 = FFileSystem->GetSessionInfo().CertificateFingerprint;
}
// Particularly to prevent reusing a wrong client certificate passphrase
// in the next login attempt
FRememberedPassword = UnicodeString();
FRememberedTunnelPassword = UnicodeString();
throw;
}
}
__finally
{
DoInformation(L"", true, 0);
}
}
catch(EFatal &)
{
throw;
}
catch(Exception & E)
{
// any exception while opening session is fatal
FatalError(&E, L"");
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::IsListenerFree(unsigned int PortNumber)
{
SOCKET Socket = socket(AF_INET, SOCK_STREAM, 0);
bool Result = (Socket != INVALID_SOCKET);
if (Result)
{
SOCKADDR_IN Address;
memset(&Address, 0, sizeof(Address));
Address.sin_family = AF_INET;
Address.sin_port = htons(static_cast<short>(PortNumber));
Address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
Result = (bind(Socket, reinterpret_cast<sockaddr *>(&Address), sizeof(Address)) == 0);
closesocket(Socket);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::OpenTunnel()
{
DebugAssert(FTunnelData == NULL);
FTunnelLocalPortNumber = FSessionData->TunnelLocalPortNumber;
if (FTunnelLocalPortNumber == 0)
{
FTunnelLocalPortNumber = Configuration->TunnelLocalPortNumberLow;
while (!IsListenerFree(FTunnelLocalPortNumber))
{
FTunnelLocalPortNumber++;
if (FTunnelLocalPortNumber > Configuration->TunnelLocalPortNumberHigh)
{
FTunnelLocalPortNumber = 0;
FatalError(NULL, FMTLOAD(TUNNEL_NO_FREE_PORT,
(Configuration->TunnelLocalPortNumberLow, Configuration->TunnelLocalPortNumberHigh)));
}
}
LogEvent(FORMAT(L"Autoselected tunnel local port number %d", (FTunnelLocalPortNumber)));
}
try
{
FTunnelData = new TSessionData(L"");
FTunnelData->Assign(StoredSessions->DefaultSettings);
FTunnelData->Name = FMTLOAD(TUNNEL_SESSION_NAME, (FSessionData->SessionName));
FTunnelData->Tunnel = false;
FTunnelData->HostName = FSessionData->TunnelHostName;
FTunnelData->PortNumber = FSessionData->TunnelPortNumber;
FTunnelData->UserName = FSessionData->TunnelUserName;
FTunnelData->Password = FSessionData->TunnelPassword;
FTunnelData->PublicKeyFile = FSessionData->TunnelPublicKeyFile;
UnicodeString HostName = FSessionData->HostNameExpanded;
if (IsIPv6Literal(HostName))
{
HostName = EscapeIPv6Literal(HostName);
}
FTunnelData->TunnelPortFwd = FORMAT(L"L%d\t%s:%d",
(FTunnelLocalPortNumber, HostName, FSessionData->PortNumber));
FTunnelData->HostKey = FSessionData->TunnelHostKey;
// inherit proxy options on the main session
FTunnelData->ProxyMethod = FSessionData->ProxyMethod;
FTunnelData->ProxyHost = FSessionData->ProxyHost;
FTunnelData->ProxyPort = FSessionData->ProxyPort;
FTunnelData->ProxyUsername = FSessionData->ProxyUsername;
FTunnelData->ProxyPassword = FSessionData->ProxyPassword;
FTunnelData->ProxyTelnetCommand = FSessionData->ProxyTelnetCommand;
FTunnelData->ProxyLocalCommand = FSessionData->ProxyLocalCommand;
FTunnelData->ProxyDNS = FSessionData->ProxyDNS;
FTunnelData->ProxyLocalhost = FSessionData->ProxyLocalhost;
// inherit most SSH options of the main session (except for private key and bugs)
FTunnelData->Compression = FSessionData->Compression;
FTunnelData->SshProt = FSessionData->SshProt;
FTunnelData->CipherList = FSessionData->CipherList;
FTunnelData->Ssh2DES = FSessionData->Ssh2DES;
FTunnelData->KexList = FSessionData->KexList;
FTunnelData->RekeyData = FSessionData->RekeyData;
FTunnelData->RekeyTime = FSessionData->RekeyTime;
FTunnelData->SshNoUserAuth = FSessionData->SshNoUserAuth;
FTunnelData->AuthGSSAPI = FSessionData->AuthGSSAPI;
FTunnelData->GSSAPIFwdTGT = FSessionData->GSSAPIFwdTGT;
FTunnelData->TryAgent = FSessionData->TryAgent;
FTunnelData->AgentFwd = FSessionData->AgentFwd;
FTunnelData->AuthTIS = FSessionData->AuthTIS;
FTunnelData->AuthKI = FSessionData->AuthKI;
FTunnelData->AuthKIPassword = FSessionData->AuthKIPassword;
// The Started argument is not used with Parent being set
FTunnelLog = new TSessionLog(this, TDateTime(), FTunnelData, Configuration);
FTunnelLog->SetParent(FLog, L"Tunnel");
FTunnelLog->ReflectSettings();
FTunnelUI = new TTunnelUI(this);
FTunnel = new TSecureShell(FTunnelUI, FTunnelData, FTunnelLog, Configuration);
FTunnelOpening = true;
try
{
FTunnel->Open();
}
__finally
{
FTunnelOpening = false;
}
FTunnelThread = new TTunnelThread(FTunnel);
}
catch(...)
{
CloseTunnel();
throw;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CloseTunnel()
{
SAFE_DESTROY_EX(TTunnelThread, FTunnelThread);
FTunnelError = FTunnel->LastTunnelError;
SAFE_DESTROY_EX(TSecureShell, FTunnel);
SAFE_DESTROY_EX(TTunnelUI, FTunnelUI);
SAFE_DESTROY_EX(TSessionLog, FTunnelLog);
SAFE_DESTROY(FTunnelData);
FTunnelLocalPortNumber = 0;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Closed()
{
if (FTunnel != NULL)
{
CloseTunnel();
}
if (OnClose)
{
TCallbackGuard Guard(this);
try
{
OnClose(this);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
FStatus = ssClosed;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ProcessGUI()
{
// Do not process GUI here, as we are called directly from a GUI loop and may
// recurse for good.
// Alternatively we may check for (FOperationProgress == NULL)
if (FNesting == 0)
{
TAutoNestingCounter NestingCounter(FNesting);
::ProcessGUI();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Progress(TFileOperationProgressType * OperationProgress)
{
if (FNesting == 0)
{
TAutoNestingCounter NestingCounter(FNesting);
OperationProgress->Progress();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Reopen(int Params)
{
TFSProtocol OrigFSProtocol = SessionData->FSProtocol;
UnicodeString PrevRemoteDirectory = SessionData->RemoteDirectory;
bool PrevReadCurrentDirectoryPending = FReadCurrentDirectoryPending;
bool PrevReadDirectoryPending = FReadDirectoryPending;
DebugAssert(!FSuspendTransaction);
bool PrevAutoReadDirectory = FAutoReadDirectory;
// here used to be a check for FExceptionOnFail being 0
// but it can happen, e.g. when we are downloading file to execute it.
// however I'm not sure why we mind having excaption-on-fail enabled here
int PrevExceptionOnFail = FExceptionOnFail;
try
{
FReadCurrentDirectoryPending = false;
FReadDirectoryPending = false;
FSuspendTransaction = true;
FExceptionOnFail = 0;
// typically, we avoid reading directory, when there is operation ongoing,
// for file list which may reference files from current directory
if (FLAGSET(Params, ropNoReadDirectory))
{
AutoReadDirectory = false;
}
// only peek, we may not be connected at all atm,
// so make sure we do not try retrieving current directory from the server
// (particularly with FTP)
UnicodeString ACurrentDirectory = PeekCurrentDirectory();
if (!ACurrentDirectory.IsEmpty())
{
SessionData->RemoteDirectory = ACurrentDirectory;
}
if (SessionData->FSProtocol == fsSFTP)
{
SessionData->FSProtocol = (FFSProtocol == cfsSCP ? fsSCPonly : fsSFTPonly);
}
Open();
}
__finally
{
SessionData->RemoteDirectory = PrevRemoteDirectory;
SessionData->FSProtocol = OrigFSProtocol;
FAutoReadDirectory = PrevAutoReadDirectory;
FReadCurrentDirectoryPending = PrevReadCurrentDirectoryPending;
FReadDirectoryPending = PrevReadDirectoryPending;
FSuspendTransaction = false;
FExceptionOnFail = PrevExceptionOnFail;
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::PromptUser(TSessionData * Data, TPromptKind Kind,
UnicodeString Name, UnicodeString Instructions, UnicodeString Prompt, bool Echo, int MaxLen, UnicodeString & Result)
{
bool AResult;
TStrings * Prompts = new TStringList;
TStrings * Results = new TStringList;
try
{
Prompts->AddObject(Prompt, (TObject *)(FLAGMASK(Echo, pupEcho)));
Results->AddObject(Result, (TObject *)MaxLen);
AResult = PromptUser(Data, Kind, Name, Instructions, Prompts, Results);
Result = Results->Strings[0];
}
__finally
{
delete Prompts;
delete Results;
}
return AResult;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::PromptUser(TSessionData * Data, TPromptKind Kind,
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts, TStrings * Results)
{
// If PromptUser is overridden in descendant class, the overridden version
// is not called when accessed via TSessionIU interface.
// So this is workaround.
// Actually no longer needed as we do not uverride DoPromptUser
// anymore in TSecondaryTerminal.
return DoPromptUser(Data, Kind, Name, Instructions, Prompts, Results);
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TTerminal::GetPasswordSource()
{
return this;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DoPromptUser(TSessionData * /*Data*/, TPromptKind Kind,
UnicodeString Name, UnicodeString Instructions, TStrings * Prompts, TStrings * Results)
{
bool AResult = false;
bool PasswordOrPassphrasePrompt = IsPasswordOrPassphrasePrompt(Kind, Prompts);
if (PasswordOrPassphrasePrompt)
{
bool & PasswordTried =
FTunnelOpening ? FRememberedTunnelPasswordTried : FRememberedPasswordTried;
if (!PasswordTried)
{
// let's expect that the main session is already authenticated and its password
// is not written after, so no locking is necessary
// (no longer true, once the main session can be reconnected)
UnicodeString APassword;
if (FTunnelOpening)
{
APassword = GetPasswordSource()->RememberedTunnelPassword;
}
else
{
APassword = GetPasswordSource()->RememberedPassword;
}
Results->Strings[0] = APassword;
if (!Results->Strings[0].IsEmpty())
{
LogEvent(L"Using remembered password.");
AResult = true;
}
PasswordTried = true;
}
}
if (!AResult)
{
if (PasswordOrPassphrasePrompt && !Configuration->RememberPassword)
{
Prompts->Objects[0] = (TObject*)(int(Prompts->Objects[0]) | pupRemember);
}
if (OnPromptUser != NULL)
{
TCallbackGuard Guard(this);
try
{
OnPromptUser(this, Kind, Name, Instructions, Prompts, Results, AResult, NULL);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
if (AResult && PasswordOrPassphrasePrompt &&
(Configuration->RememberPassword || FLAGSET(int(Prompts->Objects[0]), pupRemember)))
{
RawByteString EncryptedPassword = EncryptPassword(Results->Strings[0]);
if (FTunnelOpening)
{
GetPasswordSource()->FRememberedTunnelPassword = EncryptedPassword;
}
else
{
GetPasswordSource()->FRememberedPassword = EncryptedPassword;
}
}
}
return AResult;
}
//---------------------------------------------------------------------------
unsigned int __fastcall TTerminal::QueryUser(const UnicodeString Query,
TStrings * MoreMessages, unsigned int Answers, const TQueryParams * Params,
TQueryType QueryType)
{
LogEvent(FORMAT(L"Asking user:\n%s (%s)", (Query, (MoreMessages ? MoreMessages->CommaText : UnicodeString() ))));
unsigned int Answer = AbortAnswer(Answers);
if (FOnQueryUser)
{
TCallbackGuard Guard(this);
try
{
FOnQueryUser(this, Query, MoreMessages, Answers, Params, Answer, QueryType, NULL);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
return Answer;
}
//---------------------------------------------------------------------------
unsigned int __fastcall TTerminal::QueryUserException(const UnicodeString Query,
Exception * E, unsigned int Answers, const TQueryParams * Params,
TQueryType QueryType)
{
unsigned int Result;
UnicodeString ExMessage;
if (DebugAlwaysTrue(ExceptionMessage(E, ExMessage) || !Query.IsEmpty()))
{
TStrings * MoreMessages = new TStringList();
try
{
if (!ExMessage.IsEmpty() && !Query.IsEmpty())
{
MoreMessages->Add(UnformatMessage(ExMessage));
}
ExtException * EE = dynamic_cast<ExtException*>(E);
if ((EE != NULL) && (EE->MoreMessages != NULL))
{
MoreMessages->AddStrings(EE->MoreMessages);
}
// We know MoreMessages not to be NULL here,
// AppendExceptionStackTraceAndForget should never return true
// (indicating it had to create the string list)
DebugAlwaysFalse(AppendExceptionStackTraceAndForget(MoreMessages));
TQueryParams HelpKeywordOverrideParams;
if (Params != NULL)
{
HelpKeywordOverrideParams.Assign(*Params);
}
HelpKeywordOverrideParams.HelpKeyword =
MergeHelpKeyword(HelpKeywordOverrideParams.HelpKeyword, GetExceptionHelpKeyword(E));
Result = QueryUser(!Query.IsEmpty() ? Query : ExMessage,
MoreMessages->Count ? MoreMessages : NULL,
Answers, &HelpKeywordOverrideParams, QueryType);
}
__finally
{
delete MoreMessages;
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DisplayBanner(const UnicodeString & Banner)
{
if (OnDisplayBanner != NULL)
{
unsigned int OrigParams, Params;
if (Configuration->ForceBanners ||
Configuration->ShowBanner(SessionData->SessionKey, Banner, Params))
{
bool NeverShowAgain = false;
int Options =
FLAGMASK(Configuration->ForceBanners, boDisableNeverShowAgain);
OrigParams = Params;
TCallbackGuard Guard(this);
try
{
OnDisplayBanner(this, SessionData->SessionName, Banner, NeverShowAgain, Options, Params);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
if (!Configuration->ForceBanners && NeverShowAgain)
{
Configuration->NeverShowBanner(SessionData->SessionKey, Banner);
}
if (OrigParams != Params)
{
Configuration->SetBannerParams(SessionData->SessionKey, Params);
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::HandleExtendedException(Exception * E)
{
Log->AddException(E);
if (OnShowExtendedException != NULL)
{
TCallbackGuard Guard(this);
try
{
// the event handler may destroy 'this' ...
OnShowExtendedException(this, E, NULL);
// .. hence guard is dismissed from destructor, to make following call no-op
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ShowExtendedException(Exception * E)
{
Log->AddException(E);
if (OnShowExtendedException != NULL)
{
OnShowExtendedException(this, E, NULL);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoInformation(const UnicodeString & Str, bool Status,
int Phase)
{
if (OnInformation)
{
TCallbackGuard Guard(this);
try
{
OnInformation(this, Str, Status, Phase);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Information(const UnicodeString & Str, bool Status)
{
DoInformation(Str, Status);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoProgress(TFileOperationProgressType & ProgressData)
{
if ((Configuration->ActualLogProtocol >= 1) &&
((ProgressData.Operation == foCopy) || (ProgressData.Operation == foMove)))
{
DWORD Now = GetTickCount();
if (Now - FLastProgressLogged >= 1000)
{
LogEvent(L"Transfer progress: " + ProgressData.GetLogStr(false));
FLastProgressLogged = Now;
}
}
if (ProgressData.TransferredSize > 0)
{
FFileTransferAny = true;
}
if (OnProgress != NULL)
{
TCallbackGuard Guard(this);
try
{
OnProgress(ProgressData);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoFinished(TFileOperation Operation, TOperationSide Side, bool Temp,
const UnicodeString & FileName, bool Success, TOnceDoneOperation & OnceDoneOperation)
{
if (OnFinished != NULL)
{
TCallbackGuard Guard(this);
try
{
OnFinished(Operation, Side, Temp, FileName, Success, OnceDoneOperation);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SaveCapabilities(TFileSystemInfo & FileSystemInfo)
{
for (int Index = 0; Index < fcCount; Index++)
{
FileSystemInfo.IsCapable[Index] = IsCapable[(TFSCapability)Index];
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::GetIsCapable(TFSCapability Capability) const
{
DebugAssert(FFileSystem);
return FFileSystem->IsCapable(Capability);
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::AbsolutePath(UnicodeString Path, bool Local)
{
return FFileSystem->AbsolutePath(Path, Local);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReactOnCommand(int /*TFSCommand*/ Cmd)
{
bool ChangesDirectory = false;
bool ModifiesFiles = false;
switch ((TFSCommand)Cmd) {
case fsChangeDirectory:
case fsHomeDirectory:
ChangesDirectory = true;
break;
case fsCopyToRemote:
case fsDeleteFile:
case fsRenameFile:
case fsMoveFile:
case fsCopyFile:
case fsCreateDirectory:
case fsChangeMode:
case fsChangeGroup:
case fsChangeOwner:
case fsChangeProperties:
case fsLock:
ModifiesFiles = true;
break;
case fsAnyCommand:
ChangesDirectory = true;
ModifiesFiles = true;
break;
}
if (ChangesDirectory)
{
if (!InTransaction())
{
ReadCurrentDirectory();
if (AutoReadDirectory)
{
ReadDirectory(false);
}
}
else
{
FReadCurrentDirectoryPending = true;
if (AutoReadDirectory)
{
FReadDirectoryPending = true;
}
}
}
else if (ModifiesFiles && AutoReadDirectory && Configuration->AutoReadDirectoryAfterOp)
{
if (!InTransaction())
{
ReadDirectory(true);
}
else
{
FReadDirectoryPending = true;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::TerminalError(UnicodeString Msg)
{
TerminalError(NULL, Msg);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::TerminalError(
Exception * E, UnicodeString Msg, UnicodeString HelpKeyword)
{
throw ETerminal(E, Msg, HelpKeyword);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DoQueryReopen(Exception * E)
{
EFatal * Fatal = dynamic_cast<EFatal *>(E);
DebugAssert(Fatal != NULL);
bool Result;
if ((Fatal != NULL) && Fatal->ReopenQueried)
{
Result = false;
}
else
{
LogEvent(L"Connection was lost, asking what to do.");
TQueryParams Params(qpAllowContinueOnError);
Params.Timeout = Configuration->SessionReopenAuto;
Params.TimeoutAnswer = qaRetry;
TQueryButtonAlias Aliases[1];
Aliases[0].Button = qaRetry;
Aliases[0].Alias = LoadStr(RECONNECT_BUTTON);
Params.Aliases = Aliases;
Params.AliasesCount = LENOF(Aliases);
Result = (QueryUserException(L"", E, qaRetry | qaAbort, &Params, qtError) == qaRetry);
if (Fatal != NULL)
{
Fatal->ReopenQueried = true;
}
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::ContinueReopen(TDateTime Start)
{
return
(Configuration->SessionReopenTimeout == 0) ||
(int(double(Now() - Start) * MSecsPerDay) < Configuration->SessionReopenTimeout);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::QueryReopen(Exception * E, int Params,
TFileOperationProgressType * OperationProgress)
{
TSuspendFileOperationProgress Suspend(OperationProgress);
bool Result = DoQueryReopen(E);
if (Result)
{
TDateTime Start = Now();
do
{
try
{
Reopen(Params);
}
catch(Exception & E)
{
if (!Active)
{
Result =
ContinueReopen(Start) &&
DoQueryReopen(&E);
}
else
{
throw;
}
}
}
while (!Active && Result);
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::FileOperationLoopQuery(Exception & E,
TFileOperationProgressType * OperationProgress, const UnicodeString Message,
unsigned int Flags, UnicodeString SpecialRetry, UnicodeString HelpKeyword)
{
bool Result = false;
Log->AddException(&E);
unsigned int Answer;
bool AllowSkip = FLAGSET(Flags, folAllowSkip);
bool SkipToAllPossible = AllowSkip && (OperationProgress != NULL);
if (SkipToAllPossible && OperationProgress->SkipToAll)
{
Answer = qaSkip;
}
else
{
int Answers = qaRetry | qaAbort |
FLAGMASK(AllowSkip, qaSkip) |
FLAGMASK(SkipToAllPossible, qaAll) |
FLAGMASK(!SpecialRetry.IsEmpty(), qaYes);
TQueryParams Params(qpAllowContinueOnError | FLAGMASK(!AllowSkip, qpFatalAbort));
Params.HelpKeyword = HelpKeyword;
TQueryButtonAlias Aliases[2];
int AliasCount = 0;
if (FLAGSET(Answers, qaAll))
{
Aliases[AliasCount].Button = qaAll;
Aliases[AliasCount].Alias = LoadStr(SKIP_ALL_BUTTON);
AliasCount++;
}
if (FLAGSET(Answers, qaYes))
{
Aliases[AliasCount].Button = qaYes;
Aliases[AliasCount].Alias = SpecialRetry;
AliasCount++;
}
if (AliasCount > 0)
{
Params.Aliases = Aliases;
Params.AliasesCount = AliasCount;
}
{
TSuspendFileOperationProgress Suspend(OperationProgress);
Answer = QueryUserException(Message, &E, Answers, &Params, qtError);
}
if (Answer == qaAll)
{
DebugAssert(OperationProgress != NULL);
OperationProgress->SetSkipToAll();
Answer = qaSkip;
}
if (Answer == qaYes)
{
Result = true;
Answer = qaRetry;
}
}
if (Answer != qaRetry)
{
if ((Answer == qaAbort) && (OperationProgress != NULL))
{
OperationProgress->SetCancel(csCancel);
}
if (AllowSkip)
{
throw ESkipFile(&E, Message);
}
else
{
// this can happen only during file transfer with SCP
throw ExtException(&E, Message);
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FileOperationLoopEnd(Exception & E,
TFileOperationProgressType * OperationProgress, const UnicodeString & Message,
unsigned int Flags, const UnicodeString & SpecialRetry, const UnicodeString & HelpKeyword)
{
if ((dynamic_cast<EAbort *>(&E) != NULL) ||
(dynamic_cast<ESkipFile *>(&E) != NULL) ||
(dynamic_cast<ESkipFile *>(&E) != NULL))
{
RethrowException(&E);
}
else if (dynamic_cast<EFatal *>(&E) != NULL)
{
if (FLAGCLEAR(Flags, folRetryOnFatal))
{
RethrowException(&E);
}
else
{
DebugAssert(SpecialRetry.IsEmpty());
std::unique_ptr<Exception> E2(new EFatal(&E, Message, HelpKeyword));
if (!DoQueryReopen(E2.get()))
{
RethrowException(E2.get());
}
}
}
else
{
FileOperationLoopQuery(E, OperationProgress, Message, Flags, SpecialRetry, HelpKeyword);
}
}
//---------------------------------------------------------------------------
int __fastcall TTerminal::FileOperationLoop(TFileOperationEvent CallBackFunc,
TFileOperationProgressType * OperationProgress, unsigned int Flags,
const UnicodeString Message, void * Param1, void * Param2)
{
DebugAssert(CallBackFunc);
int Result;
FILE_OPERATION_LOOP_BEGIN
{
Result = CallBackFunc(Param1, Param2);
}
FILE_OPERATION_LOOP_END_EX(Message, Flags);
return Result;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::TranslateLockedPath(UnicodeString Path, bool Lock)
{
if (SessionData->LockInHome && !Path.IsEmpty() && (Path[1] == L'/'))
{
if (Lock)
{
if (Path.SubString(1, FLockDirectory.Length()) == FLockDirectory)
{
Path.Delete(1, FLockDirectory.Length());
if (Path.IsEmpty()) Path = L"/";
}
}
else
{
Path = UnixExcludeTrailingBackslash(FLockDirectory + Path);
}
}
return Path;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ClearCaches()
{
FDirectoryCache->Clear();
if (FDirectoryChangesCache != NULL)
{
FDirectoryChangesCache->Clear();
}
if (FCommandSession != NULL)
{
FCommandSession->ClearCaches();
}
FFileSystem->ClearCaches();
FEncryptedFileNames.clear();
FFoldersScannedForEncryptedFiles.clear();
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ClearCachedFileList(const UnicodeString Path,
bool SubDirs)
{
FDirectoryCache->ClearFileList(Path, SubDirs);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::AddCachedFileList(TRemoteFileList * FileList)
{
FDirectoryCache->AddFileList(FileList);
}
//---------------------------------------------------------------------------
TRemoteFileList * __fastcall TTerminal::DirectoryFileList(const UnicodeString Path, TDateTime Timestamp, bool CanLoad)
{
TRemoteFileList * Result = NULL;
if (UnixSamePath(FFiles->Directory, Path))
{
if (Timestamp < FFiles->Timestamp)
{
Result = new TRemoteFileList();
FFiles->DuplicateTo(Result);
}
}
else
{
if (FDirectoryCache->HasNewerFileList(Path, Timestamp))
{
Result = new TRemoteFileList();
DebugAlwaysTrue(FDirectoryCache->GetFileList(Path, Result));
}
// do not attempt to load file list if there is cached version,
// only absence of cached version indicates that we consider
// the directory content obsolete
else if (CanLoad && !FDirectoryCache->HasFileList(Path))
{
Result = new TRemoteFileList();
Result->Directory = Path;
try
{
ReadDirectory(Result);
}
catch(...)
{
SAFE_DESTROY(Result);
throw;
}
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SetCurrentDirectory(UnicodeString value)
{
DebugAssert(FFileSystem);
value = TranslateLockedPath(value, false);
if (value != FFileSystem->CurrentDirectory)
{
ChangeDirectory(value);
}
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetCurrentDirectory()
{
if (FFileSystem != NULL)
{
// there's occasional crash when assigning FFileSystem->CurrentDirectory
// to FCurrentDirectory, splitting the assignment to two statemets
// to locate the crash more closely
UnicodeString CurrentDirectory = FFileSystem->CurrentDirectory;
FCurrentDirectory = CurrentDirectory;
if (FCurrentDirectory.IsEmpty())
{
ReadCurrentDirectory();
}
}
UnicodeString Result = TranslateLockedPath(FCurrentDirectory, true);
return Result;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::PeekCurrentDirectory()
{
if (FFileSystem)
{
FCurrentDirectory = FFileSystem->CurrentDirectory;
}
UnicodeString Result = TranslateLockedPath(FCurrentDirectory, true);
return Result;
}
//---------------------------------------------------------------------------
const TRemoteTokenList * __fastcall TTerminal::GetGroups()
{
DebugAssert(FFileSystem);
LookupUsersGroups();
return &FGroups;
}
//---------------------------------------------------------------------------
const TRemoteTokenList * __fastcall TTerminal::GetUsers()
{
DebugAssert(FFileSystem);
LookupUsersGroups();
return &FUsers;
}
//---------------------------------------------------------------------------
const TRemoteTokenList * __fastcall TTerminal::GetMembership()
{
DebugAssert(FFileSystem);
LookupUsersGroups();
return &FMembership;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetUserName() const
{
// in future might also be implemented to detect username similar to GetUserGroups
DebugAssert(FFileSystem != NULL);
UnicodeString Result = FFileSystem->GetUserName();
// Is empty also when stored username was used
if (Result.IsEmpty())
{
Result = SessionData->UserNameExpanded;
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::GetAreCachesEmpty() const
{
return FDirectoryCache->IsEmpty &&
((FDirectoryChangesCache == NULL) || FDirectoryChangesCache->IsEmpty);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoInitializeLog()
{
if (FOnInitializeLog)
{
TCallbackGuard Guard(this);
try
{
FOnInitializeLog(this);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoChangeDirectory()
{
if (FOnChangeDirectory)
{
TCallbackGuard Guard(this);
try
{
FOnChangeDirectory(this);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoReadDirectory(bool ReloadOnly)
{
if (FOnReadDirectory)
{
TCallbackGuard Guard(this);
try
{
FOnReadDirectory(this, ReloadOnly);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoStartReadDirectory()
{
if (FOnStartReadDirectory)
{
TCallbackGuard Guard(this);
try
{
FOnStartReadDirectory(this);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoReadDirectoryProgress(int Progress, int ResolvedLinks, bool & Cancel)
{
if (FReadingCurrentDirectory && (FOnReadDirectoryProgress != NULL))
{
TCallbackGuard Guard(this);
try
{
FOnReadDirectoryProgress(this, Progress, ResolvedLinks, Cancel);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
if (FOnFindingFile != NULL)
{
TCallbackGuard Guard(this);
try
{
FOnFindingFile(this, L"", Cancel);
Guard.Verify();
}
catch (Exception & E)
{
if (!Guard.Verify(&E))
{
throw;
}
}
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::InTransaction()
{
return (FInTransaction > 0) && !FSuspendTransaction;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::BeginTransaction()
{
if (FInTransaction == 0)
{
FReadCurrentDirectoryPending = false;
FReadDirectoryPending = false;
}
FInTransaction++;
if (FCommandSession != NULL)
{
FCommandSession->BeginTransaction();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::EndTransaction()
{
DoEndTransaction(false);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoEndTransaction(bool Inform)
{
if (FInTransaction == 0)
TerminalError(L"Can't end transaction, not in transaction");
DebugAssert(FInTransaction > 0);
FInTransaction--;
// it connection was closed due to fatal error during transaction, do nothing
if (Active)
{
if (FInTransaction == 0)
{
try
{
if (FReadCurrentDirectoryPending)
{
ReadCurrentDirectory();
}
if (FReadDirectoryPending)
{
if (Inform)
{
DoInformation(LoadStr(STATUS_OPEN_DIRECTORY), true);
}
ReadDirectory(!FReadCurrentDirectoryPending);
}
}
__finally
{
FReadCurrentDirectoryPending = false;
FReadDirectoryPending = false;
}
}
}
if (FCommandSession != NULL)
{
FCommandSession->EndTransaction();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SetExceptionOnFail(bool value)
{
if (value)
{
FExceptionOnFail++;
}
else
{
if (FExceptionOnFail == 0)
throw Exception(L"ExceptionOnFail is already zero.");
FExceptionOnFail--;
}
if (FCommandSession != NULL)
{
FCommandSession->FExceptionOnFail = FExceptionOnFail;
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::GetExceptionOnFail() const
{
return (bool)(FExceptionOnFail > 0);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FatalAbort()
{
FatalError(NULL, "");
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FatalError(Exception * E, UnicodeString Msg, UnicodeString HelpKeyword)
{
bool SecureShellActive = (FSecureShell != NULL) && FSecureShell->Active;
if (Active || SecureShellActive)
{
// We log this instead of exception handler, because Close() would
// probably cause exception handler to loose pointer to TShellLog()
LogEvent(L"Attempt to close connection due to fatal exception:");
Log->Add(llException, Msg);
Log->AddException(E);
if (Active)
{
Close();
}
// this may happen if failure of authentication of SSH, owned by terminal yet
// (because the protocol was not decided yet), is detected by us (not by putty).
// e.g. not verified host key
if (SecureShellActive)
{
FSecureShell->Close();
}
}
if (FCallbackGuard != NULL)
{
FCallbackGuard->FatalError(E, Msg, HelpKeyword);
}
else
{
throw ESshFatal(E, Msg, HelpKeyword);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CommandError(Exception * E, const UnicodeString Msg)
{
CommandError(E, Msg, 0);
}
//---------------------------------------------------------------------------
unsigned int __fastcall TTerminal::CommandError(Exception * E, const UnicodeString Msg,
unsigned int Answers, const UnicodeString HelpKeyword)
{
// may not be, particularly when TTerminal::Reopen is being called
// from within OnShowExtendedException handler
DebugAssert(FCallbackGuard == NULL);
unsigned int Result = 0;
if (E && E->InheritsFrom(__classid(EFatal)))
{
FatalError(E, Msg, HelpKeyword);
}
else if (E && E->InheritsFrom(__classid(EAbort)))
{
// resent EAbort exception
Abort();
}
else if (ExceptionOnFail)
{
throw ECommand(E, Msg, HelpKeyword);
}
else if (!Answers)
{
ECommand * ECmd = new ECommand(E, Msg, HelpKeyword);
try
{
HandleExtendedException(ECmd);
}
__finally
{
delete ECmd;
}
}
else
{
// small hack to enable "skip to all" for TRetryOperationLoop
bool CanSkip = FLAGSET(Answers, qaSkip) && (OperationProgress != NULL);
if (CanSkip && OperationProgress->SkipToAll)
{
Result = qaSkip;
}
else
{
TQueryParams Params(qpAllowContinueOnError, HelpKeyword);
TQueryButtonAlias Aliases[1];
if (CanSkip)
{
Aliases[0].Button = qaAll;
Aliases[0].Alias = LoadStr(SKIP_ALL_BUTTON);
Params.Aliases = Aliases;
Params.AliasesCount = LENOF(Aliases);
Answers |= qaAll;
}
Result = QueryUserException(Msg, E, Answers, &Params, qtError);
if (Result == qaAll)
{
DebugAssert(OperationProgress != NULL);
OperationProgress->SetSkipToAll();
Result = qaSkip;
}
}
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::HandleException(Exception * E)
{
if (ExceptionOnFail)
{
return false;
}
else
{
Log->AddException(E);
return true;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CloseOnCompletion(
TOnceDoneOperation Operation, const UnicodeString & Message,
const UnicodeString & TargetLocalPath, const UnicodeString & DestLocalFileName)
{
Configuration->Usage->Inc(L"ClosesOnCompletion");
LogEvent(L"Closing session after completed operation (as requested by user)");
Close();
throw ESshTerminate(NULL,
Message.IsEmpty() ? UnicodeString(LoadStr(CLOSED_ON_COMPLETION)) : Message,
Operation, TargetLocalPath, DestLocalFileName);
}
//---------------------------------------------------------------------------
TBatchOverwrite __fastcall TTerminal::EffectiveBatchOverwrite(
const UnicodeString & SourceFullFileName, const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, bool Special)
{
TBatchOverwrite Result;
if (Special &&
(FLAGSET(Params, cpResume) || CopyParam->ResumeTransfer(SourceFullFileName)))
{
Result = boResume;
}
else if (FLAGSET(Params, cpAppend))
{
Result = boAppend;
}
else if (CopyParam->NewerOnly &&
(((OperationProgress->Side == osLocal) && IsCapable[fcNewerOnlyUpload]) ||
(OperationProgress->Side != osLocal)))
{
// no way to change batch overwrite mode when CopyParam->NewerOnly is on
Result = boOlder;
}
else if (FLAGSET(Params, cpNoConfirmation) || !Configuration->ConfirmOverwriting)
{
// no way to change batch overwrite mode when overwrite confirmations are off
DebugAssert(OperationProgress->BatchOverwrite == boNo);
Result = boAll;
}
else
{
Result = OperationProgress->BatchOverwrite;
if (!Special &&
((Result == boOlder) || (Result == boAlternateResume) || (Result == boResume)))
{
Result = boNo;
}
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CheckRemoteFile(
const UnicodeString & FileName, const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress)
{
bool Result;
OperationProgress->LockUserSelections();
try
{
Result = (EffectiveBatchOverwrite(FileName, CopyParam, Params, OperationProgress, true) != boAll);
}
__finally
{
OperationProgress->UnlockUserSelections();
}
return Result;
}
//---------------------------------------------------------------------------
unsigned int __fastcall TTerminal::ConfirmFileOverwrite(
const UnicodeString & SourceFullFileName, const UnicodeString & TargetFileName,
const TOverwriteFileParams * FileParams, unsigned int Answers, TQueryParams * QueryParams,
TOperationSide Side, const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
UnicodeString Message)
{
unsigned int Result;
TBatchOverwrite BatchOverwrite;
bool CanAlternateResume =
(FileParams != NULL) &&
(FileParams->DestSize < FileParams->SourceSize) &&
!OperationProgress->AsciiTransfer;
OperationProgress->LockUserSelections();
try
{
// duplicated in TSFTPFileSystem::SFTPConfirmOverwrite
BatchOverwrite = EffectiveBatchOverwrite(SourceFullFileName, CopyParam, Params, OperationProgress, true);
bool Applicable = true;
switch (BatchOverwrite)
{
case boOlder:
Applicable = (FileParams != NULL);
break;
case boAlternateResume:
Applicable = CanAlternateResume;
break;
case boResume:
Applicable = CanAlternateResume;
break;
}
if (!Applicable)
{
TBatchOverwrite ABatchOverwrite = EffectiveBatchOverwrite(SourceFullFileName, CopyParam, Params, OperationProgress, false);
DebugAssert(BatchOverwrite != ABatchOverwrite);
BatchOverwrite = ABatchOverwrite;
}
if (BatchOverwrite == boNo)
{
// particularly with parallel transfers, the overal operation can be already cancelled by other parallel operation
if (OperationProgress->Cancel > csContinue)
{
Result = qaCancel;
}
else
{
if (Message.IsEmpty())
{
// Side refers to destination side here
Message = FMTLOAD((Side == osLocal ? LOCAL_FILE_OVERWRITE2 :
REMOTE_FILE_OVERWRITE2), (TargetFileName, TargetFileName));
}
if (FileParams != NULL)
{
Message = FMTLOAD(FILE_OVERWRITE_DETAILS, (Message,
FormatSize(FileParams->SourceSize),
DefaultStr(UserModificationStr(FileParams->SourceTimestamp, FileParams->SourcePrecision), LoadStr(TIME_UNKNOWN)),
FormatSize(FileParams->DestSize),
DefaultStr(UserModificationStr(FileParams->DestTimestamp, FileParams->DestPrecision), LoadStr(TIME_UNKNOWN))));
}
if (DebugAlwaysTrue(QueryParams->HelpKeyword.IsEmpty()))
{
QueryParams->HelpKeyword = HELP_OVERWRITE;
}
Result = QueryUser(Message, NULL, Answers, QueryParams);
switch (Result)
{
case qaNeverAskAgain:
Configuration->ConfirmOverwriting = false;
Result = qaYes;
break;
case qaYesToAll:
BatchOverwrite = boAll;
break;
case qaAll:
BatchOverwrite = boOlder;
break;
case qaNoToAll:
BatchOverwrite = boNone;
break;
}
// we user has not selected another batch overwrite mode,
// keep the current one. note that we may get here even
// when batch overwrite was selected already, but it could not be applied
// to current transfer (see condition above)
if (BatchOverwrite != boNo)
{
OperationProgress->SetBatchOverwrite(BatchOverwrite);
}
}
}
}
__finally
{
OperationProgress->UnlockUserSelections();
}
if (BatchOverwrite != boNo)
{
switch (BatchOverwrite)
{
case boAll:
Result = qaYes;
break;
case boNone:
Result = qaNo;
break;
case boOlder:
if (FileParams == NULL)
{
Result = qaNo;
}
else
{
TModificationFmt Precision = LessDateTimePrecision(FileParams->SourcePrecision, FileParams->DestPrecision);
TDateTime ReducedSourceTimestamp =
ReduceDateTimePrecision(FileParams->SourceTimestamp, Precision);
TDateTime ReducedDestTimestamp =
ReduceDateTimePrecision(FileParams->DestTimestamp, Precision);
Result =
(CompareFileTime(ReducedSourceTimestamp, ReducedDestTimestamp) > 0) ?
qaYes : qaNo;
LogEvent(FORMAT(L"Source file timestamp is [%s], destination timestamp is [%s], will%s overwrite",
(StandardTimestamp(ReducedSourceTimestamp),
StandardTimestamp(ReducedDestTimestamp),
((Result == qaYes) ? L"" : L" not"))));
}
break;
case boAlternateResume:
DebugAssert(CanAlternateResume);
Result = qaSkip; // ugh
break;
case boAppend:
Result = qaRetry;
break;
case boResume:
Result = qaRetry;
break;
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FileModified(const TRemoteFile * File,
const UnicodeString FileName, bool ClearDirectoryChange)
{
UnicodeString ParentDirectory;
UnicodeString Directory;
if (SessionData->CacheDirectories || SessionData->CacheDirectoryChanges)
{
if ((File != NULL) && (File->Directory != NULL))
{
if (File->IsDirectory)
{
Directory = File->Directory->FullDirectory + File->FileName;
}
ParentDirectory = File->Directory->Directory;
}
else if (!FileName.IsEmpty())
{
ParentDirectory = UnixExtractFilePath(FileName);
if (ParentDirectory.IsEmpty())
{
ParentDirectory = CurrentDirectory;
}
// this case for scripting
if ((File != NULL) && File->IsDirectory)
{
Directory = UnixIncludeTrailingBackslash(ParentDirectory) +
UnixExtractFileName(File->FileName);
}
}
}
if (SessionData->CacheDirectories)
{
if (!Directory.IsEmpty())
{
DirectoryModified(Directory, true);
}
if (!ParentDirectory.IsEmpty())
{
DirectoryModified(ParentDirectory, false);
}
}
if (SessionData->CacheDirectoryChanges && ClearDirectoryChange)
{
if (!Directory.IsEmpty())
{
FDirectoryChangesCache->ClearDirectoryChange(Directory);
FDirectoryChangesCache->ClearDirectoryChangeTarget(Directory);
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DirectoryModified(const UnicodeString Path, bool SubDirs)
{
if (Path.IsEmpty())
{
ClearCachedFileList(CurrentDirectory, SubDirs);
}
else
{
ClearCachedFileList(Path, SubDirs);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DirectoryLoaded(TRemoteFileList * FileList)
{
AddCachedFileList(FileList);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReloadDirectory()
{
if (SessionData->CacheDirectories)
{
DirectoryModified(CurrentDirectory, false);
}
if (SessionData->CacheDirectoryChanges)
{
DebugAssert(FDirectoryChangesCache != NULL);
FDirectoryChangesCache->ClearDirectoryChange(CurrentDirectory);
}
ReadCurrentDirectory();
FReadCurrentDirectoryPending = false;
ReadDirectory(true);
FReadDirectoryPending = false;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::RefreshDirectory()
{
if (!SessionData->CacheDirectories)
{
LogEvent(L"Not refreshing directory, caching is off.");
}
else if (FDirectoryCache->HasNewerFileList(CurrentDirectory, FFiles->Timestamp))
{
// Second parameter was added to allow (rather force) using the cache.
// Before, the directory was reloaded always, it seems useless,
// has it any reason?
ReadDirectory(true, true);
FReadDirectoryPending = false;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::EnsureNonExistence(const UnicodeString FileName)
{
// if filename doesn't contain path, we check for existence of file
if ((UnixExtractFileDir(FileName).IsEmpty()) &&
UnixSamePath(CurrentDirectory, FFiles->Directory))
{
TRemoteFile *File = FFiles->FindFile(FileName);
if (File)
{
if (File->IsDirectory) throw ECommand(NULL, FMTLOAD(RENAME_CREATE_DIR_EXISTS, (FileName)));
else throw ECommand(NULL, FMTLOAD(RENAME_CREATE_FILE_EXISTS, (FileName)));
}
}
}
//---------------------------------------------------------------------------
void __fastcall inline TTerminal::LogEvent(const UnicodeString & Str)
{
if (Log->Logging)
{
Log->Add(llMessage, Str);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::RollbackAction(TSessionAction & Action,
TFileOperationProgressType * OperationProgress, Exception * E)
{
// ESkipFile without "cancel" is file skip,
// and we do not want to record skipped actions.
// But ESkipFile with "cancel" is abort and we want to record that.
// Note that TSCPFileSystem modifies the logic of RollbackAction little bit.
if ((dynamic_cast<ESkipFile *>(E) != NULL) &&
((OperationProgress == NULL) ||
(OperationProgress->Cancel == csContinue)))
{
Action.Cancel();
}
else
{
Action.Rollback(E);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoStartup()
{
LogEvent(L"Doing startup conversation with host.");
BeginTransaction();
try
{
DoInformation(LoadStr(STATUS_STARTUP), true);
// Make sure that directory would be loaded at last
FReadCurrentDirectoryPending = true;
FReadDirectoryPending = AutoReadDirectory;
FFileSystem->DoStartup();
LookupUsersGroups();
if (!SessionData->RemoteDirectory.IsEmpty())
{
if (SessionData->UpdateDirectories)
{
ExceptionOnFail = true;
try
{
ChangeDirectory(SessionData->RemoteDirectory);
}
catch (...)
{
if (!Active)
{
LogEvent(L"Configured initial remote directory cannot be opened, staying in the home directory.");
throw;
}
}
ExceptionOnFail = false;
}
else
{
ChangeDirectory(SessionData->RemoteDirectory);
}
}
}
__finally
{
DoEndTransaction(true);
}
LogEvent(L"Startup conversation with host finished.");
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReadCurrentDirectory()
{
DebugAssert(FFileSystem);
try
{
// reset flag is case we are called externally (like from console dialog)
FReadCurrentDirectoryPending = false;
LogEvent(L"Getting current directory name.");
UnicodeString OldDirectory = FFileSystem->CurrentDirectory;
FFileSystem->ReadCurrentDirectory();
ReactOnCommand(fsCurrentDirectory);
if (SessionData->CacheDirectoryChanges)
{
DebugAssert(FDirectoryChangesCache != NULL);
if (!CurrentDirectory.IsEmpty() && !FLastDirectoryChange.IsEmpty() && (CurrentDirectory != OldDirectory))
{
FDirectoryChangesCache->AddDirectoryChange(OldDirectory,
FLastDirectoryChange, CurrentDirectory);
}
// not to broke the cache, if the next directory change would not
// be initialized by ChangeDirectory(), which sets it
// (HomeDirectory() particularly)
FLastDirectoryChange = L"";
}
if (OldDirectory.IsEmpty())
{
FLockDirectory = (SessionData->LockInHome ?
FFileSystem->CurrentDirectory : UnicodeString(L""));
}
if (OldDirectory != FFileSystem->CurrentDirectory) DoChangeDirectory();
}
catch (Exception &E)
{
CommandError(&E, LoadStr(READ_CURRENT_DIR_ERROR));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReadDirectory(bool ReloadOnly, bool ForceCache)
{
bool LoadedFromCache = false;
if (SessionData->CacheDirectories && FDirectoryCache->HasFileList(CurrentDirectory))
{
if (ReloadOnly && !ForceCache)
{
LogEvent(L"Cached directory not reloaded.");
}
else
{
DoStartReadDirectory();
try
{
LoadedFromCache = FDirectoryCache->GetFileList(CurrentDirectory, FFiles);
}
__finally
{
DoReadDirectory(ReloadOnly);
}
if (LoadedFromCache)
{
LogEvent(L"Directory content loaded from cache.");
}
else
{
LogEvent(L"Cached Directory content has been removed.");
}
}
}
if (!LoadedFromCache)
{
DoStartReadDirectory();
FReadingCurrentDirectory = true;
bool Cancel = false; // dummy
DoReadDirectoryProgress(0, 0, Cancel);
try
{
TRemoteDirectory * Files = new TRemoteDirectory(this, FFiles);
try
{
Files->Directory = CurrentDirectory;
CustomReadDirectory(Files);
}
__finally
{
DoReadDirectoryProgress(-1, 0, Cancel);
FReadingCurrentDirectory = false;
TRemoteDirectory * OldFiles = FFiles;
FFiles = Files;
try
{
DoReadDirectory(ReloadOnly);
}
__finally
{
// delete only after loading new files to dir view,
// not to destroy the file objects that the view holds
// (can be issue in multi threaded environment, such as when the
// terminal is reconnecting in the terminal thread)
delete OldFiles;
}
if (Active)
{
if (SessionData->CacheDirectories)
{
DirectoryLoaded(FFiles);
}
}
}
}
catch (Exception &E)
{
CommandError(&E, FmtLoadStr(LIST_DIR_ERROR, ARRAYOFCONST((FFiles->Directory))));
}
}
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetRemoteFileInfo(TRemoteFile * File)
{
return
FORMAT(L"%s;%s;%d;%s;%d;%s;%s;%s;%d",
(File->FileName, File->Type, File->Size, StandardTimestamp(File->Modification), int(File->ModificationFmt),
File->Owner.LogText, File->Group.LogText, File->Rights->Text,
File->Attr));
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LogRemoteFile(TRemoteFile * File)
{
// optimization
if (Log->Logging)
{
LogEvent(GetRemoteFileInfo(File));
}
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::FormatFileDetailsForLog(const UnicodeString & FileName, TDateTime Modification, __int64 Size)
{
UnicodeString Result;
// optimization
if (Log->Logging)
{
Result = FORMAT(L"'%s' [%s] [%s]", (FileName, (Modification != TDateTime() ? StandardTimestamp(Modification) : UnicodeString(L"n/a")), IntToStr(Size)));
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LogFileDetails(const UnicodeString & FileName, TDateTime Modification, __int64 Size)
{
// optimization
if (Log->Logging)
{
LogEvent(FORMAT("File: %s", (FormatFileDetailsForLog(FileName, Modification, Size))));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LogFileDone(TFileOperationProgressType * OperationProgress, const UnicodeString & DestFileName)
{
if (FDestFileName.IsEmpty())
{
FDestFileName = DestFileName;
}
else
{
FMultipleDestinationFiles = true;
}
// optimization
if (Log->Logging)
{
LogEvent(FORMAT("Transfer done: '%s' => '%s' [%s]", (OperationProgress->FullFileName, DestFileName, IntToStr(OperationProgress->TransferredSize))));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CustomReadDirectory(TRemoteFileList * FileList)
{
DebugAssert(FileList);
DebugAssert(FFileSystem);
// To match FTP upload/download, we also limit directory listing.
// For simiplicity, we limit it unconditionally, for all protocols for any kind of errors.
bool FileTransferAny = false;
TRobustOperationLoop RobustLoop(this, OperationProgress, &FileTransferAny);
do
{
try
{
// Record even an attempt, to avoid listing a directory over and over again, when it is not readable actually
if (IsEncryptingFiles())
{
FFoldersScannedForEncryptedFiles.insert(FileList->Directory);
}
FFileSystem->ReadDirectory(FileList);
}
catch (Exception & E)
{
// Do not retry for initial listing of directory,
// we instead retry whole connection attempt,
// what would be done anyway (but Open is not ready for recursion).
if ((FOpening > 0) ||
!RobustLoop.TryReopen(E))
{
throw;
}
}
}
while (RobustLoop.Retry());
if (Log->Logging && (Configuration->ActualLogProtocol >= 0))
{
for (int Index = 0; Index < FileList->Count; Index++)
{
LogRemoteFile(FileList->Files[Index]);
}
}
ReactOnCommand(fsListDirectory);
}
//---------------------------------------------------------------------------
TRemoteFileList * __fastcall TTerminal::ReadDirectoryListing(UnicodeString Directory, const TFileMasks & Mask)
{
TRemoteFileList * FileList;
TRetryOperationLoop RetryLoop(this);
do
{
FileList = NULL;
TLsSessionAction Action(ActionLog, AbsolutePath(Directory, true));
try
{
FileList = DoReadDirectoryListing(Directory, false);
if (FileList != NULL)
{
int Index = 0;
while (Index < FileList->Count)
{
TRemoteFile * File = FileList->Files[Index];
TFileMasks::TParams Params;
Params.Size = File->Size;
Params.Modification = File->Modification;
// Have to use UnicodeString(), instead of L"", as with that
// overload with (UnicodeString, bool, bool, TParams*) wins
if (!Mask.Matches(File->FileName, false, UnicodeString(), &Params))
{
FileList->Delete(Index);
}
else
{
Index++;
}
}
Action.FileList(FileList);
}
}
catch(Exception & E)
{
RetryLoop.Error(E, Action);
}
}
while (RetryLoop.Retry());
return FileList;
}
//---------------------------------------------------------------------------
TRemoteFile * __fastcall TTerminal::ReadFileListing(UnicodeString Path)
{
TRemoteFile * File;
TRetryOperationLoop RetryLoop(this);
do
{
File = NULL;
TStatSessionAction Action(ActionLog, AbsolutePath(Path, true));
try
{
// reset caches
AnnounceFileListOperation();
ReadFile(Path, File);
Action.File(File);
}
catch(Exception & E)
{
RetryLoop.Error(E, Action);
}
}
while (RetryLoop.Retry());
return File;
}
//---------------------------------------------------------------------------
TRemoteFileList * __fastcall TTerminal::CustomReadDirectoryListing(UnicodeString Directory, bool UseCache)
{
TRemoteFileList * FileList = NULL;
TRetryOperationLoop RetryLoop(this);
do
{
try
{
FileList = DoReadDirectoryListing(Directory, UseCache);
}
catch(Exception & E)
{
RetryLoop.Error(E);
}
}
while (RetryLoop.Retry());
return FileList;
}
//---------------------------------------------------------------------------
TRemoteFileList * __fastcall TTerminal::DoReadDirectoryListing(UnicodeString Directory, bool UseCache)
{
TRemoteFileList * FileList = new TRemoteFileList();
try
{
bool Cache = UseCache && SessionData->CacheDirectories;
bool LoadedFromCache = Cache && FDirectoryCache->HasFileList(Directory);
if (LoadedFromCache)
{
LoadedFromCache = FDirectoryCache->GetFileList(Directory, FileList);
}
if (!LoadedFromCache)
{
FileList->Directory = Directory;
ExceptionOnFail = true;
try
{
ReadDirectory(FileList);
}
__finally
{
ExceptionOnFail = false;
}
if (Cache)
{
AddCachedFileList(FileList);
}
}
}
catch(...)
{
delete FileList;
throw;
}
return FileList;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DeleteContentsIfDirectory(
const UnicodeString & FileName, const TRemoteFile * File, int Params, TRmSessionAction & Action)
{
bool Dir = (File != NULL) && File->IsDirectory && CanRecurseToDirectory(File);
if (Dir && FLAGCLEAR(Params, dfNoRecursive))
{
try
{
ProcessDirectory(FileName, DeleteFile, &Params);
}
catch(...)
{
Action.Cancel();
throw;
}
}
return Dir && !File->IsSymLink;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ProcessDirectory(const UnicodeString DirName,
TProcessFileEvent CallBackFunc, void * Param, bool UseCache, bool IgnoreErrors)
{
TRemoteFileList * FileList = NULL;
if (IgnoreErrors)
{
ExceptionOnFail = true;
try
{
try
{
FileList = CustomReadDirectoryListing(DirName, UseCache);
}
catch(...)
{
if (!Active)
{
throw;
}
}
}
__finally
{
ExceptionOnFail = false;
}
}
else
{
FileList = CustomReadDirectoryListing(DirName, UseCache);
}
// skip if directory listing fails and user selects "skip"
if (FileList)
{
try
{
UnicodeString Directory = UnixIncludeTrailingBackslash(DirName);
TRemoteFile * File;
for (int Index = 0; Index < FileList->Count; Index++)
{
File = FileList->Files[Index];
if (!File->IsParentDirectory && !File->IsThisDirectory)
{
CallBackFunc(Directory + File->FileName, File, Param);
// We should catch ESkipFile here as we do in ProcessFiles.
// Now we have to handle ESkipFile in every callback implementation.
}
}
}
__finally
{
delete FileList;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReadDirectory(TRemoteFileList * FileList)
{
try
{
CustomReadDirectory(FileList);
}
catch (Exception &E)
{
CommandError(&E, FmtLoadStr(LIST_DIR_ERROR, ARRAYOFCONST((FileList->Directory))));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReadSymlink(TRemoteFile * SymlinkFile,
TRemoteFile *& File)
{
DebugAssert(FFileSystem);
try
{
LogEvent(FORMAT(L"Reading symlink \"%s\".", (SymlinkFile->FileName)));
FFileSystem->ReadSymlink(SymlinkFile, File);
ReactOnCommand(fsReadSymlink);
}
catch (Exception &E)
{
CommandError(&E, FMTLOAD(READ_SYMLINK_ERROR, (SymlinkFile->FileName)));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReadFile(const UnicodeString FileName,
TRemoteFile *& File)
{
DebugAssert(FFileSystem);
File = NULL;
try
{
LogEvent(FORMAT(L"Listing file \"%s\".", (FileName)));
FFileSystem->ReadFile(FileName, File);
ReactOnCommand(fsListFile);
LogRemoteFile(File);
}
catch (Exception &E)
{
if (File) delete File;
File = NULL;
CommandError(&E, FMTLOAD(CANT_GET_ATTRS, (FileName)));
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::FileExists(const UnicodeString FileName, TRemoteFile ** AFile)
{
bool Result;
TRemoteFile * File = NULL;
try
{
ExceptionOnFail = true;
try
{
ReadFile(UnixExcludeTrailingBackslash(FileName), File);
}
__finally
{
ExceptionOnFail = false;
}
if (AFile != NULL)
{
*AFile = File;
}
else
{
delete File;
}
Result = true;
}
catch(...)
{
if (Active)
{
Result = false;
}
else
{
throw;
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::AnnounceFileListOperation()
{
FFileSystem->AnnounceFileListOperation();
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::ProcessFiles(TStrings * FileList,
TFileOperation Operation, TProcessFileEvent ProcessFile, void * Param,
TOperationSide Side, bool Ex)
{
DebugAssert(FileList);
bool Result = false;
TOnceDoneOperation OnceDoneOperation = odoIdle;
try
{
TFileOperationProgressType Progress(&DoProgress, &DoFinished);
Progress.Start(Operation, Side, FileList->Count);
FOperationProgress = &Progress;
try
{
if (Side == osRemote)
{
DebugAssert(FFileSystem != NULL);
BeginTransaction();
}
try
{
int Index = 0;
UnicodeString FileName;
bool Success;
while ((Index < FileList->Count) && (Progress.Cancel == csContinue))
{
FileName = FileList->Strings[Index];
try
{
try
{
Success = false;
if (!Ex)
{
ProcessFile(FileName, (TRemoteFile *)FileList->Objects[Index], Param);
}
else
{
// not used anymore
TProcessFileEventEx ProcessFileEx = (TProcessFileEventEx)ProcessFile;
ProcessFileEx(FileName, (TRemoteFile *)FileList->Objects[Index], Param, Index);
}
Success = true;
}
__finally
{
Progress.Finish(FileName, Success, OnceDoneOperation);
}
}
catch(ESkipFile & E)
{
TSuspendFileOperationProgress Suspend(OperationProgress);
if (!HandleException(&E))
{
throw;
}
}
Index++;
}
}
__finally
{
if (Side == osRemote)
{
EndTransaction();
}
}
if (Progress.Cancel == csContinue)
{
Result = true;
}
}
__finally
{
FOperationProgress = NULL;
Progress.Stop();
}
}
catch (...)
{
OnceDoneOperation = odoIdle;
// this was missing here. was it by purpose?
// without it any error message is lost
throw;
}
if (OnceDoneOperation != odoIdle)
{
CloseOnCompletion(OnceDoneOperation);
}
return Result;
}
//---------------------------------------------------------------------------
// not used anymore
bool __fastcall TTerminal::ProcessFilesEx(TStrings * FileList, TFileOperation Operation,
TProcessFileEventEx ProcessFile, void * Param, TOperationSide Side)
{
return ProcessFiles(FileList, Operation, TProcessFileEvent(ProcessFile),
Param, Side, true);
}
//---------------------------------------------------------------------------
TStrings * __fastcall TTerminal::GetFixedPaths()
{
DebugAssert(FFileSystem != NULL);
return FFileSystem->GetFixedPaths();
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::GetResolvingSymlinks()
{
return SessionData->ResolveSymlinks && IsCapable[fcResolveSymlink];
}
//---------------------------------------------------------------------------
TUsableCopyParamAttrs __fastcall TTerminal::UsableCopyParamAttrs(int Params)
{
TUsableCopyParamAttrs Result;
Result.General =
FLAGMASK(!IsCapable[fcTextMode], cpaNoTransferMode) |
FLAGMASK(!IsCapable[fcModeChanging], cpaNoRights) |
FLAGMASK(!IsCapable[fcModeChanging], cpaNoPreserveReadOnly) |
FLAGMASK(FLAGSET(Params, cpDelete), cpaNoClearArchive) |
FLAGMASK(!IsCapable[fcIgnorePermErrors], cpaNoIgnorePermErrors) |
// the following three are never supported for download,
// so when they are not suppored for upload too,
// set them in General flags, so that they do not get enabled on
// Synchronize dialog.
FLAGMASK(!IsCapable[fcModeChangingUpload], cpaNoRights) |
FLAGMASK(!IsCapable[fcRemoveCtrlZUpload], cpaNoRemoveCtrlZ) |
FLAGMASK(!IsCapable[fcRemoveBOMUpload], cpaNoRemoveBOM) |
FLAGMASK(!IsCapable[fcPreservingTimestampDirs], cpaNoPreserveTimeDirs) |
FLAGMASK(!IsCapable[fcResumeSupport], cpaNoResumeSupport) |
FLAGMASK(!IsEncryptingFiles(), cpaNoEncryptNewFiles);
Result.Download = Result.General | cpaNoClearArchive |
cpaNoIgnorePermErrors |
// May be already set in General flags, but it's unconditional here
cpaNoRights | cpaNoRemoveCtrlZ | cpaNoRemoveBOM | cpaNoEncryptNewFiles;
Result.Upload = Result.General | cpaNoPreserveReadOnly |
FLAGMASK(!IsCapable[fcPreservingTimestampUpload], cpaNoPreserveTime);
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::IsRecycledFile(UnicodeString FileName)
{
bool Result = !SessionData->RecycleBinPath.IsEmpty();
if (Result)
{
UnicodeString Path = UnixExtractFilePath(FileName);
if (Path.IsEmpty())
{
Path = CurrentDirectory;
}
Result = UnixSamePath(Path, SessionData->RecycleBinPath);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::RecycleFile(UnicodeString FileName,
const TRemoteFile * File)
{
if (FileName.IsEmpty())
{
DebugAssert(File != NULL);
FileName = File->FileName;
}
if (!IsRecycledFile(FileName))
{
LogEvent(FORMAT(L"Moving file \"%s\" to remote recycle bin '%s'.",
(FileName, SessionData->RecycleBinPath)));
TMoveFileParams Params;
Params.Target = SessionData->RecycleBinPath;
Params.FileMask = FORMAT(L"*-%s.*", (FormatDateTime(L"yyyymmdd-hhnnss", Now())));
MoveFile(FileName, File, &Params);
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::TryStartOperationWithFile(
const UnicodeString & FileName, TFileOperation Operation1, TFileOperation Operation2)
{
bool Result = true;
if ((OperationProgress != NULL) &&
((OperationProgress->Operation == Operation1) ||
((Operation2 != foNone) && (OperationProgress->Operation == Operation2))))
{
if (OperationProgress->Cancel != csContinue)
{
Result = false;
}
else
{
OperationProgress->SetFile(FileName);
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::StartOperationWithFile(
const UnicodeString & FileName, TFileOperation Operation1, TFileOperation Operation2)
{
if (!TryStartOperationWithFile(FileName, Operation1, Operation2))
{
Abort();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DeleteFile(UnicodeString FileName,
const TRemoteFile * File, void * AParams)
{
if (FileName.IsEmpty() && File)
{
FileName = File->FileName;
}
StartOperationWithFile(FileName, foDelete);
int Params = (AParams != NULL) ? *((int*)AParams) : 0;
bool Recycle =
FLAGCLEAR(Params, dfForceDelete) &&
(SessionData->DeleteToRecycleBin != FLAGSET(Params, dfAlternative)) &&
!SessionData->RecycleBinPath.IsEmpty();
if (Recycle && !IsRecycledFile(FileName))
{
RecycleFile(FileName, File);
}
else
{
LogEvent(FORMAT(L"Deleting file \"%s\".", (FileName)));
FileModified(File, FileName, true);
DoDeleteFile(FileName, File, Params);
// Forget if file was or was not encrypted and use user preferences, if we ever recreate it.
FEncryptedFileNames.erase(AbsolutePath(FileName, true));
ReactOnCommand(fsDeleteFile);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoDeleteFile(const UnicodeString FileName,
const TRemoteFile * File, int Params)
{
TRetryOperationLoop RetryLoop(this);
do
{
TRmSessionAction Action(ActionLog, AbsolutePath(FileName, true));
try
{
DebugAssert(FFileSystem);
// 'File' parameter: SFTPFileSystem needs to know if file is file or directory
FFileSystem->DeleteFile(FileName, File, Params, Action);
}
catch(Exception & E)
{
RetryLoop.Error(E, Action, FMTLOAD(DELETE_FILE_ERROR, (FileName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DeleteFiles(TStrings * FilesToDelete, int Params)
{
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor);
FUseBusyCursor = false;
// TODO: avoid resolving symlinks while reading subdirectories.
// Resolving does not work anyway for relative symlinks in subdirectories
// (at least for SFTP).
return ProcessFiles(FilesToDelete, foDelete, DeleteFile, &Params);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DeleteLocalFile(UnicodeString FileName,
const TRemoteFile * /*File*/, void * Params)
{
StartOperationWithFile(FileName, foDelete);
if (OnDeleteLocalFile == NULL)
{
RecursiveDeleteFileChecked(FileName, false);
}
else
{
OnDeleteLocalFile(FileName, FLAGSET(*((int*)Params), dfAlternative));
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DeleteLocalFiles(TStrings * FileList, int Params)
{
return ProcessFiles(FileList, foDelete, DeleteLocalFile, &Params, osLocal);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CustomCommandOnFile(UnicodeString FileName,
const TRemoteFile * File, void * AParams)
{
TCustomCommandParams * Params = ((TCustomCommandParams *)AParams);
if (FileName.IsEmpty() && File)
{
FileName = File->FileName;
}
StartOperationWithFile(FileName, foCustomCommand);
LogEvent(FORMAT(L"Executing custom command \"%s\" (%d) on file \"%s\".",
(Params->Command, Params->Params, FileName)));
FileModified(File, FileName);
DoCustomCommandOnFile(FileName, File, Params->Command, Params->Params,
Params->OutputEvent);
ReactOnCommand(fsAnyCommand);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCustomCommandOnFile(UnicodeString FileName,
const TRemoteFile * File, UnicodeString Command, int Params,
TCaptureOutputEvent OutputEvent)
{
TRetryOperationLoop RetryLoop(this);
do
{
try
{
if (IsCapable[fcAnyCommand])
{
DebugAssert(FFileSystem);
DebugAssert(fcShellAnyCommand);
FFileSystem->CustomCommandOnFile(FileName, File, Command, Params, OutputEvent);
}
else
{
DebugAssert(CommandSessionOpened);
DebugAssert(FCommandSession->FSProtocol == cfsSCP);
LogEvent(L"Executing custom command on command session.");
if (FCommandSession->CurrentDirectory != CurrentDirectory)
{
FCommandSession->CurrentDirectory = CurrentDirectory;
// We are likely in transaction, so ReadCurrentDirectory won't get called
// until transaction ends. But we need to know CurrentDirectory to
// expand !/ pattern.
// Doing this only, when current directory of the main and secondary shell differs,
// what would be the case before the first file in transaction.
// Otherwise we would be reading pwd before every time as the
// CustomCommandOnFile on its own sets FReadCurrentDirectoryPending
if (FCommandSession->FReadCurrentDirectoryPending)
{
FCommandSession->ReadCurrentDirectory();
}
}
FCommandSession->FFileSystem->CustomCommandOnFile(FileName, File, Command,
Params, OutputEvent);
}
}
catch(Exception & E)
{
RetryLoop.Error(E, FMTLOAD(CUSTOM_COMMAND_ERROR, (Command, FileName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CustomCommandOnFiles(UnicodeString Command,
int Params, TStrings * Files, TCaptureOutputEvent OutputEvent)
{
if (!TRemoteCustomCommand().IsFileListCommand(Command))
{
TCustomCommandParams AParams;
AParams.Command = Command;
AParams.Params = Params;
AParams.OutputEvent = OutputEvent;
ProcessFiles(Files, foCustomCommand, CustomCommandOnFile, &AParams);
}
else
{
UnicodeString FileList;
for (int i = 0; i < Files->Count; i++)
{
TRemoteFile * File = static_cast<TRemoteFile *>(Files->Objects[i]);
bool Dir = File->IsDirectory && CanRecurseToDirectory(File);
if (!Dir || FLAGSET(Params, ccApplyToDirectories))
{
if (!FileList.IsEmpty())
{
FileList += L" ";
}
FileList += L"\"" + ShellDelimitStr(Files->Strings[i], L'"') + L"\"";
}
}
TCustomCommandData Data(this);
UnicodeString Cmd =
TRemoteCustomCommand(Data, CurrentDirectory, L"", FileList).
Complete(Command, true);
if (!DoOnCustomCommand(Cmd))
{
DoAnyCommand(Cmd, OutputEvent, NULL);
}
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DoOnCustomCommand(const UnicodeString & Command)
{
bool Result = false;
if (FOnCustomCommand != NULL)
{
FOnCustomCommand(this, Command, Result);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ChangeFileProperties(UnicodeString FileName,
const TRemoteFile * File, /*const TRemoteProperties*/ void * Properties)
{
TRemoteProperties * RProperties = (TRemoteProperties *)Properties;
DebugAssert(RProperties && !RProperties->Valid.Empty());
if (FileName.IsEmpty() && File)
{
FileName = File->FileName;
}
StartOperationWithFile(FileName, foSetProperties);
if (Log->Logging)
{
LogEvent(FORMAT(L"Changing properties of \"%s\" (%s)",
(FileName, BooleanToEngStr(RProperties->Recursive))));
if (RProperties->Valid.Contains(vpRights))
{
LogEvent(FORMAT(L" - mode: \"%s\"", (RProperties->Rights.ModeStr)));
}
if (RProperties->Valid.Contains(vpGroup))
{
LogEvent(FORMAT(L" - group: %s", (RProperties->Group.LogText)));
}
if (RProperties->Valid.Contains(vpOwner))
{
LogEvent(FORMAT(L" - owner: %s", (RProperties->Owner.LogText)));
}
if (RProperties->Valid.Contains(vpModification))
{
LogEvent(FORMAT(L" - modification: \"%s\"",
(FormatDateTime(L"dddddd tt",
UnixToDateTime(RProperties->Modification, SessionData->DSTMode)))));
}
if (RProperties->Valid.Contains(vpLastAccess))
{
LogEvent(FORMAT(L" - last access: \"%s\"",
(FormatDateTime(L"dddddd tt",
UnixToDateTime(RProperties->LastAccess, SessionData->DSTMode)))));
}
}
FileModified(File, FileName);
DoChangeFileProperties(FileName, File, RProperties);
ReactOnCommand(fsChangeProperties);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoChangeFileProperties(const UnicodeString FileName,
const TRemoteFile * File, const TRemoteProperties * Properties)
{
TRetryOperationLoop RetryLoop(this);
do
{
TChmodSessionAction Action(ActionLog, AbsolutePath(FileName, true));
try
{
DebugAssert(FFileSystem);
FFileSystem->ChangeFileProperties(FileName, File, Properties, Action);
}
catch(Exception & E)
{
RetryLoop.Error(E, Action, FMTLOAD(CHANGE_PROPERTIES_ERROR, (FileName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ChangeFilesProperties(TStrings * FileList,
const TRemoteProperties * Properties)
{
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor);
FUseBusyCursor = false;
AnnounceFileListOperation();
ProcessFiles(FileList, foSetProperties, ChangeFileProperties, (void *)Properties);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::LoadFilesProperties(TStrings * FileList)
{
// see comment in TSFTPFileSystem::IsCapable
bool Result =
IsCapable[fcLoadingAdditionalProperties] &&
FFileSystem->LoadFilesProperties(FileList);
if (Result && SessionData->CacheDirectories &&
(FileList->Count > 0) &&
(dynamic_cast<TRemoteFile *>(FileList->Objects[0])->Directory == FFiles))
{
AddCachedFileList(FFiles);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCalculateFileSize(UnicodeString FileName,
const TRemoteFile * File, void * Param)
{
TCalculateSizeParams * AParams = static_cast<TCalculateSizeParams*>(Param);
if (AParams->Stats->FoundFiles != NULL)
{
UnicodeString DirPath = UnixExtractFilePath(UnixExcludeTrailingBackslash(File->FullFileName));
if ((DirPath != AParams->LastDirPath) ||
(AParams->Files == NULL))
{
AParams->Files = new TCollectedFileList();
AParams->LastDirPath = DirPath;
AParams->Stats->FoundFiles->AddObject(AParams->LastDirPath, AParams->Files);
}
}
CalculateFileSize(FileName, File, Param);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CalculateFileSize(UnicodeString FileName,
const TRemoteFile * File, /*TCalculateSizeParams*/ void * Param)
{
DebugAssert(Param);
DebugAssert(File);
TCalculateSizeParams * AParams = static_cast<TCalculateSizeParams*>(Param);
if (FileName.IsEmpty())
{
FileName = File->FileName;
}
if (!TryStartOperationWithFile(FileName, foCalculateSize))
{
AParams->Result = false;
// Combined with csIgnoreErrors, this will not abort completelly, but we get called again
// with next sibling of our parent directory (and we get again to this branch, throwing again)
Abort();
}
bool AllowTransfer = (AParams->CopyParam == NULL);
if (!AllowTransfer)
{
TFileMasks::TParams MaskParams;
MaskParams.Size = File->Size;
MaskParams.Modification = File->Modification;
UnicodeString BaseFileName =
GetBaseFileName(UnixExcludeTrailingBackslash(File->FullFileName));
AllowTransfer = AParams->CopyParam->AllowTransfer(
BaseFileName, osRemote, File->IsDirectory, MaskParams);
}
if (AllowTransfer)
{
int CollectionIndex = -1;
if (AParams->Files != NULL)
{
UnicodeString FullFileName = UnixExcludeTrailingBackslash(File->FullFileName);
CollectionIndex = AParams->Files->Add(FullFileName, File->Duplicate(), File->IsDirectory);
}
if (File->IsDirectory)
{
if (CanRecurseToDirectory(File))
{
if (!AParams->AllowDirs)
{
AParams->Result = false;
}
else
{
LogEvent(FORMAT(L"Getting size of directory \"%s\"", (FileName)));
// pass in full path so we get it back in file list for AllowTransfer() exclusion
if (!DoCalculateDirectorySize(File->FullFileName, File, AParams))
{
if (CollectionIndex >= 0)
{
AParams->Files->DidNotRecurse(CollectionIndex);
}
}
}
}
AParams->Stats->Directories++;
}
else
{
AParams->Size += File->Size;
AParams->Stats->Files++;
}
if (File->IsSymLink)
{
AParams->Stats->SymLinks++;
}
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DoCalculateDirectorySize(const UnicodeString FileName,
const TRemoteFile * /*File*/, TCalculateSizeParams * Params)
{
bool Result = false;
TRetryOperationLoop RetryLoop(this);
do
{
try
{
ProcessDirectory(FileName, CalculateFileSize, Params);
Result = true;
}
catch(Exception & E)
{
// We can probably replace the csIgnoreErrors with IgnoreErrors argument of the ProcessDirectory
if (!Active || ((Params->Params & csIgnoreErrors) == 0))
{
RetryLoop.Error(E, FMTLOAD(CALCULATE_SIZE_ERROR, (FileName)));
}
}
}
while (RetryLoop.Retry());
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CalculateFilesSize(TStrings * FileList,
__int64 & Size, int Params, const TCopyParamType * CopyParam,
bool AllowDirs, TCalculateSizeStats & Stats)
{
// With FTP protocol, we may use DSIZ command from
// draft-peterson-streamlined-ftp-command-extensions-10
// Implemented by Serv-U FTP.
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor);
FUseBusyCursor = false;
TCalculateSizeParams Param;
Param.Size = 0;
Param.Params = Params;
Param.CopyParam = CopyParam;
Param.Stats = &Stats;
Param.AllowDirs = AllowDirs;
Param.Result = true;
Param.Files = NULL;
ProcessFiles(FileList, foCalculateSize, DoCalculateFileSize, &Param);
Size = Param.Size;
return Param.Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CalculateFilesChecksum(const UnicodeString & Alg,
TStrings * FileList, TStrings * Checksums,
TCalculatedChecksumEvent OnCalculatedChecksum)
{
FFileSystem->CalculateFilesChecksum(Alg, FileList, Checksums, OnCalculatedChecksum);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::RenameFile(const TRemoteFile * File,
const UnicodeString NewName, bool CheckExistence)
{
DebugAssert(File && File->Directory == FFiles);
bool Proceed = true;
// if filename doesn't contain path, we check for existence of file
if ((File->FileName != NewName) && CheckExistence &&
Configuration->ConfirmOverwriting &&
UnixSamePath(CurrentDirectory, FFiles->Directory))
{
TRemoteFile * DuplicateFile = FFiles->FindFile(NewName);
if (DuplicateFile)
{
UnicodeString QuestionFmt;
if (DuplicateFile->IsDirectory)
{
QuestionFmt = LoadStr(DIRECTORY_OVERWRITE);
}
else
{
QuestionFmt = LoadStr(FILE_OVERWRITE);
}
TQueryParams Params(qpNeverAskAgainCheck);
UnicodeString Question = MainInstructions(FORMAT(QuestionFmt, (NewName)));
unsigned int Result = QueryUser(Question, NULL,
qaYes | qaNo, &Params);
if (Result == qaNeverAskAgain)
{
Proceed = true;
Configuration->ConfirmOverwriting = false;
}
else
{
Proceed = (Result == qaYes);
}
}
}
if (Proceed)
{
FileModified(File, File->FileName);
LogEvent(FORMAT(L"Renaming file \"%s\" to \"%s\".", (File->FileName, NewName)));
DoRenameFile(File->FileName, File, NewName, false);
ReactOnCommand(fsRenameFile);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoRenameFile(const UnicodeString FileName, const TRemoteFile * File,
const UnicodeString NewName, bool Move)
{
TRetryOperationLoop RetryLoop(this);
do
{
TMvSessionAction Action(ActionLog, AbsolutePath(FileName, true), AbsolutePath(NewName, true));
try
{
DebugAssert(FFileSystem);
FFileSystem->RenameFile(FileName, File, NewName);
}
catch(Exception & E)
{
UnicodeString Message = FMTLOAD(Move ? MOVE_FILE_ERROR : RENAME_FILE_ERROR, (FileName, NewName));
RetryLoop.Error(E, Action, Message);
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::MoveFile(const UnicodeString FileName,
const TRemoteFile * File, /*const TMoveFileParams*/ void * Param)
{
StartOperationWithFile(FileName, foRemoteMove, foDelete);
DebugAssert(Param != NULL);
const TMoveFileParams & Params = *static_cast<const TMoveFileParams*>(Param);
UnicodeString NewName = UnixIncludeTrailingBackslash(Params.Target) +
MaskFileName(UnixExtractFileName(FileName), Params.FileMask);
LogEvent(FORMAT(L"Moving file \"%s\" to \"%s\".", (FileName, NewName)));
FileModified(File, FileName);
DoRenameFile(FileName, File, NewName, true);
ReactOnCommand(fsMoveFile);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::MoveFiles(TStrings * FileList, const UnicodeString Target,
const UnicodeString FileMask)
{
TMoveFileParams Params;
Params.Target = Target;
Params.FileMask = FileMask;
DirectoryModified(Target, true);
bool Result;
BeginTransaction();
try
{
Result = ProcessFiles(FileList, foRemoteMove, MoveFile, &Params);
}
__finally
{
if (Active)
{
UnicodeString WithTrailing = UnixIncludeTrailingBackslash(CurrentDirectory);
bool PossiblyMoved = false;
// check if we was moving current directory.
// this is just optimization to avoid checking existence of current
// directory after each move operation.
for (int Index = 0; !PossiblyMoved && (Index < FileList->Count); Index++)
{
const TRemoteFile * File =
dynamic_cast<const TRemoteFile *>(FileList->Objects[Index]);
// File can be NULL, and filename may not be full path,
// but currently this is the only way we can move (at least in GUI)
// current directory
if ((File != NULL) &&
File->IsDirectory &&
((CurrentDirectory.SubString(1, FileList->Strings[Index].Length()) == FileList->Strings[Index]) &&
((FileList->Strings[Index].Length() == CurrentDirectory.Length()) ||
(CurrentDirectory[FileList->Strings[Index].Length() + 1] == L'/'))))
{
PossiblyMoved = true;
}
}
if (PossiblyMoved && !FileExists(CurrentDirectory))
{
UnicodeString NearestExisting = CurrentDirectory;
do
{
NearestExisting = UnixExtractFileDir(NearestExisting);
}
while (!IsUnixRootPath(NearestExisting) && !FileExists(NearestExisting));
ChangeDirectory(NearestExisting);
}
}
EndTransaction();
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCopyFile(const UnicodeString FileName, const TRemoteFile * File,
const UnicodeString NewName)
{
TRetryOperationLoop RetryLoop(this);
do
{
TCpSessionAction Action(ActionLog, AbsolutePath(FileName, true), AbsolutePath(NewName, true));
try
{
DebugAssert(FFileSystem);
if (IsCapable[fcRemoteCopy])
{
FFileSystem->CopyFile(FileName, File, NewName);
}
else
{
DebugAssert(CommandSessionOpened);
DebugAssert(FCommandSession->FSProtocol == cfsSCP);
LogEvent(L"Copying file on command session.");
FCommandSession->CurrentDirectory = CurrentDirectory;
FCommandSession->FFileSystem->CopyFile(FileName, File, NewName);
}
}
catch(Exception & E)
{
RetryLoop.Error(E, Action, FMTLOAD(COPY_FILE_ERROR, (FileName, NewName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CopyFile(const UnicodeString FileName,
const TRemoteFile * File, /*const TMoveFileParams*/ void * Param)
{
StartOperationWithFile(FileName, foRemoteCopy);
DebugAssert(Param != NULL);
const TMoveFileParams & Params = *static_cast<const TMoveFileParams*>(Param);
UnicodeString NewName = UnixIncludeTrailingBackslash(Params.Target) +
MaskFileName(UnixExtractFileName(FileName), Params.FileMask);
LogEvent(FORMAT(L"Copying file \"%s\" to \"%s\".", (FileName, NewName)));
DoCopyFile(FileName, File, NewName);
ReactOnCommand(fsCopyFile);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CopyFiles(TStrings * FileList, const UnicodeString Target,
const UnicodeString FileMask)
{
TMoveFileParams Params;
Params.Target = Target;
Params.FileMask = FileMask;
DirectoryModified(Target, true);
return ProcessFiles(FileList, foRemoteCopy, CopyFile, &Params);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CreateDirectory(const UnicodeString & DirName, const TRemoteProperties * Properties)
{
DebugAssert(FFileSystem);
DebugAssert(Properties != NULL);
EnsureNonExistence(DirName);
FileModified(NULL, DirName);
LogEvent(FORMAT(L"Creating directory \"%s\".", (DirName)));
bool Encrypt = Properties->Valid.Contains(vpEncrypt) && Properties->Encrypt;
DoCreateDirectory(DirName, Encrypt);
TValidProperties RemainingPropeties = Properties->Valid - (TValidProperties() << vpEncrypt);
if (!RemainingPropeties.Empty())
{
DoChangeFileProperties(DirName, NULL, Properties);
}
ReactOnCommand(fsCreateDirectory);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCreateDirectory(const UnicodeString & DirName, bool Encrypt)
{
TRetryOperationLoop RetryLoop(this);
do
{
TMkdirSessionAction Action(ActionLog, AbsolutePath(DirName, true));
try
{
DebugAssert(FFileSystem);
FFileSystem->CreateDirectory(DirName, Encrypt);
}
catch(Exception & E)
{
RetryLoop.Error(E, Action, FMTLOAD(CREATE_DIR_ERROR, (DirName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CreateLink(const UnicodeString FileName,
const UnicodeString PointTo, bool Symbolic)
{
DebugAssert(FFileSystem);
EnsureNonExistence(FileName);
if (SessionData->CacheDirectories)
{
DirectoryModified(CurrentDirectory, false);
}
LogEvent(FORMAT(L"Creating link \"%s\" to \"%s\" (symbolic: %s).",
(FileName, PointTo, BooleanToEngStr(Symbolic))));
DoCreateLink(FileName, PointTo, Symbolic);
ReactOnCommand(fsCreateDirectory);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCreateLink(const UnicodeString FileName,
const UnicodeString PointTo, bool Symbolic)
{
TRetryOperationLoop RetryLoop(this);
do
{
try
{
DebugAssert(FFileSystem);
FFileSystem->CreateLink(FileName, PointTo, Symbolic);
}
catch(Exception & E)
{
RetryLoop.Error(E, FMTLOAD(CREATE_LINK_ERROR, (FileName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::HomeDirectory()
{
DebugAssert(FFileSystem);
try
{
LogEvent(L"Changing directory to home directory.");
FFileSystem->HomeDirectory();
ReactOnCommand(fsHomeDirectory);
}
catch (Exception &E)
{
CommandError(&E, LoadStr(CHANGE_HOMEDIR_ERROR));
}
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetHomeDirectory()
{
return FFileSystem->GetHomeDirectory();
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ChangeDirectory(const UnicodeString Directory)
{
DebugAssert(FFileSystem);
try
{
UnicodeString CachedDirectory;
DebugAssert(!SessionData->CacheDirectoryChanges || (FDirectoryChangesCache != NULL));
// never use directory change cache during startup, this ensures, we never
// end-up initially in non-existing directory
if ((Status == ssOpened) &&
SessionData->CacheDirectoryChanges &&
FDirectoryChangesCache->GetDirectoryChange(PeekCurrentDirectory(),
Directory, CachedDirectory))
{
LogEvent(FORMAT(L"Cached directory change via \"%s\" to \"%s\".",
(Directory, CachedDirectory)));
FFileSystem->CachedChangeDirectory(CachedDirectory);
}
else
{
LogEvent(FORMAT(L"Changing directory to \"%s\".", (Directory)));
FFileSystem->ChangeDirectory(Directory);
}
FLastDirectoryChange = Directory;
ReactOnCommand(fsChangeDirectory);
}
catch (Exception &E)
{
CommandError(&E, FMTLOAD(CHANGE_DIR_ERROR, (Directory)));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LookupUsersGroups()
{
if (!FUsersGroupsLookedup && (SessionData->LookupUserGroups != asOff) &&
IsCapable[fcUserGroupListing])
{
DebugAssert(FFileSystem);
try
{
FUsersGroupsLookedup = true;
LogEvent(L"Looking up groups and users.");
FFileSystem->LookupUsersGroups();
ReactOnCommand(fsLookupUsersGroups);
if (Log->Logging)
{
FGroups.Log(this, L"groups");
FMembership.Log(this, L"membership");
FUsers.Log(this, L"users");
}
}
catch (Exception &E)
{
if (!Active || (SessionData->LookupUserGroups == asOn))
{
CommandError(&E, LoadStr(LOOKUP_GROUPS_ERROR));
}
}
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::AllowedAnyCommand(const UnicodeString Command)
{
return !Command.Trim().IsEmpty();
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::GetCommandSessionOpened()
{
// consider secondary terminal open in "ready" state only
// so we never do keepalives on it until it is completely initialized
return (FCommandSession != NULL) &&
(FCommandSession->Status == ssOpened);
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TTerminal::CreateSecondarySession(const UnicodeString & Name, TSessionData * SessionData)
{
std::unique_ptr<TTerminal> Result(new TSecondaryTerminal(this, SessionData, Configuration, Name));
Result->AutoReadDirectory = false;
Result->FExceptionOnFail = FExceptionOnFail;
Result->OnQueryUser = OnQueryUser;
Result->OnPromptUser = OnPromptUser;
Result->OnShowExtendedException = OnShowExtendedException;
Result->OnProgress = OnProgress;
Result->OnFinished = OnFinished;
Result->OnInformation = OnInformation;
Result->OnCustomCommand = OnCustomCommand;
// do not copy OnDisplayBanner to avoid it being displayed
return Result.release();
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FillSessionDataForCode(TSessionData * Data)
{
const TSessionInfo & SessionInfo = GetSessionInfo();
if (!SessionInfo.HostKeyFingerprintSHA256.IsEmpty())
{
Data->HostKey = SessionInfo.HostKeyFingerprintSHA256;
}
else if (!SessionInfo.CertificateFingerprint.IsEmpty())
{
Data->HostKey = SessionInfo.CertificateFingerprint;
}
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TTerminal::GetCommandSession()
{
if ((FCommandSession != NULL) && !FCommandSession->Active)
{
SAFE_DESTROY(FCommandSession);
}
if (FCommandSession == NULL)
{
// transaction cannot be started yet to allow proper matching transaction
// levels between main and command session
DebugAssert(FInTransaction == 0);
std::unique_ptr<TSessionData> CommandSessionData(SessionData->Clone());
CommandSessionData->RemoteDirectory = CurrentDirectory;
CommandSessionData->FSProtocol = fsSCPonly;
CommandSessionData->ClearAliases = false;
CommandSessionData->UnsetNationalVars = false;
CommandSessionData->LookupUserGroups = asOff;
FCommandSession = CreateSecondarySession(L"Shell", CommandSessionData.get());
FCommandSession->AutoReadDirectory = false;
FCommandSession->FExceptionOnFail = FExceptionOnFail;
FCommandSession->OnQueryUser = OnQueryUser;
FCommandSession->OnPromptUser = OnPromptUser;
FCommandSession->OnShowExtendedException = OnShowExtendedException;
FCommandSession->OnProgress = OnProgress;
FCommandSession->OnFinished = OnFinished;
FCommandSession->OnInformation = OnInformation;
FCommandSession->OnCustomCommand = OnCustomCommand;
// do not copy OnDisplayBanner to avoid it being displayed
}
return FCommandSession;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::AnyCommand(const UnicodeString Command,
TCaptureOutputEvent OutputEvent)
{
#pragma warn -inl
class TOutputProxy
{
public:
__fastcall TOutputProxy(TCallSessionAction & Action, TCaptureOutputEvent OutputEvent) :
FAction(Action),
FOutputEvent(OutputEvent)
{
}
void __fastcall Output(const UnicodeString & Str, TCaptureOutputType OutputType)
{
switch (OutputType)
{
case cotOutput:
FAction.AddOutput(Str, false);
break;
case cotError:
FAction.AddOutput(Str, true);
break;
case cotExitCode:
FAction.ExitCode(StrToInt(Str));
break;
}
if (FOutputEvent != NULL)
{
FOutputEvent(Str, OutputType);
}
}
private:
TCallSessionAction & FAction;
TCaptureOutputEvent FOutputEvent;
};
#pragma warn .inl
TCallSessionAction Action(ActionLog, Command, CurrentDirectory);
TOutputProxy ProxyOutputEvent(Action, OutputEvent);
DoAnyCommand(Command, ProxyOutputEvent.Output, &Action);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoAnyCommand(const UnicodeString Command,
TCaptureOutputEvent OutputEvent, TCallSessionAction * Action)
{
DebugAssert(FFileSystem);
try
{
DirectoryModified(CurrentDirectory, false);
if (IsCapable[fcAnyCommand])
{
LogEvent(L"Executing user defined command.");
FFileSystem->AnyCommand(Command, OutputEvent);
}
else
{
DebugAssert(CommandSessionOpened);
DebugAssert(FCommandSession->FSProtocol == cfsSCP);
LogEvent(L"Executing user defined command on command session.");
FCommandSession->CurrentDirectory = CurrentDirectory;
FCommandSession->FFileSystem->AnyCommand(Command, OutputEvent);
FCommandSession->FFileSystem->ReadCurrentDirectory();
// synchronize pwd (by purpose we lose transaction optimization here)
ChangeDirectory(FCommandSession->CurrentDirectory);
}
ReactOnCommand(fsAnyCommand);
}
catch (Exception &E)
{
if (Action != NULL)
{
RollbackAction(*Action, NULL, &E);
}
if (ExceptionOnFail || (E.InheritsFrom(__classid(EFatal)))) throw;
else HandleExtendedException(&E);
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::DoCreateLocalFile(const UnicodeString FileName,
TFileOperationProgressType * OperationProgress, HANDLE * AHandle,
bool NoConfirmation)
{
bool Result = true;
bool Done;
unsigned int CreateAttr = FILE_ATTRIBUTE_NORMAL;
do
{
*AHandle = CreateFile(ApiPath(FileName).c_str(), GENERIC_WRITE, FILE_SHARE_READ,
NULL, CREATE_ALWAYS, CreateAttr, 0);
Done = (*AHandle != INVALID_HANDLE_VALUE);
if (!Done)
{
// save the error, otherwise it gets overwritten by call to FileExists
int LastError = GetLastError();
int FileAttr;
if (::FileExists(ApiPath(FileName)) &&
(((FileAttr = FileGetAttrFix(ApiPath(FileName))) & (faReadOnly | faHidden)) != 0))
{
if (FLAGSET(FileAttr, faReadOnly))
{
OperationProgress->LockUserSelections();
try
{
if (OperationProgress->BatchOverwrite == boNone)
{
Result = false;
}
else if ((OperationProgress->BatchOverwrite != boAll) && !NoConfirmation)
{
unsigned int Answer;
{
TSuspendFileOperationProgress Suspend(OperationProgress);
Answer = QueryUser(
MainInstructions(FMTLOAD(READ_ONLY_OVERWRITE, (FileName))), NULL,
qaYes | qaNo | qaCancel | qaYesToAll | qaNoToAll, 0);
}
switch (Answer) {
case qaYesToAll: OperationProgress->SetBatchOverwrite(boAll); break;
case qaCancel: OperationProgress->SetCancel(csCancel); // continue on next case
case qaNoToAll: OperationProgress->SetBatchOverwrite(boNone);
case qaNo: Result = false; break;
}
}
}
__finally
{
OperationProgress->UnlockUserSelections();
}
}
else
{
DebugAssert(FLAGSET(FileAttr, faHidden));
Result = true;
}
if (Result)
{
CreateAttr |=
FLAGMASK(FLAGSET(FileAttr, faHidden), FILE_ATTRIBUTE_HIDDEN) |
FLAGMASK(FLAGSET(FileAttr, faReadOnly), FILE_ATTRIBUTE_READONLY);
FILE_OPERATION_LOOP_BEGIN
{
if (FileSetAttr(ApiPath(FileName), FileAttr & ~(faReadOnly | faHidden)) != 0)
{
RaiseLastOSError();
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (FileName)));
}
else
{
Done = true;
}
}
else
{
RaiseLastOSError(LastError);
}
}
}
while (!Done);
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CreateLocalFile(const UnicodeString FileName,
TFileOperationProgressType * OperationProgress, HANDLE * AHandle,
bool NoConfirmation)
{
DebugAssert(AHandle);
bool Result = true;
FILE_OPERATION_LOOP_BEGIN
{
Result = DoCreateLocalFile(FileName, OperationProgress, AHandle, NoConfirmation);
}
FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_FILE_ERROR, (FileName)));
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::OpenLocalFile(const UnicodeString FileName,
unsigned int Access, int * AAttrs, HANDLE * AHandle, __int64 * ACTime,
__int64 * AMTime, __int64 * AATime, __int64 * ASize,
bool TryWriteReadOnly)
{
int Attrs = 0;
HANDLE Handle = 0;
FILE_OPERATION_LOOP_BEGIN
{
UnicodeString FileNameApi = ApiPath(FileName);
Attrs = FileGetAttrFix(FileNameApi);
if (Attrs < 0)
{
RaiseLastOSError();
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
if (FLAGCLEAR(Attrs, faDirectory) || (AHandle == NULL))
{
bool NoHandle = false;
if (!TryWriteReadOnly && (Access == GENERIC_WRITE) &&
((Attrs & faReadOnly) != 0))
{
Access = GENERIC_READ;
NoHandle = true;
}
FILE_OPERATION_LOOP_BEGIN
{
DWORD Flags = FLAGMASK(FLAGSET(Attrs, faDirectory), FILE_FLAG_BACKUP_SEMANTICS);
Handle = CreateFile(ApiPath(FileName).c_str(), Access,
Access == GENERIC_READ ? FILE_SHARE_READ | FILE_SHARE_WRITE : FILE_SHARE_READ,
NULL, OPEN_EXISTING, Flags, 0);
if (Handle == INVALID_HANDLE_VALUE)
{
Handle = 0;
RaiseLastOSError();
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(OPENFILE_ERROR, (FileName)));
try
{
if (AATime || AMTime || ACTime)
{
FILETIME ATime;
FILETIME MTime;
FILETIME CTime;
// Get last file access and modification time
FILE_OPERATION_LOOP_BEGIN
{
THROWOSIFFALSE(GetFileTime(Handle, &CTime, &ATime, &MTime));
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_GET_ATTRS, (FileName)));
if (ACTime)
{
*ACTime = ConvertTimestampToUnixSafe(CTime, SessionData->DSTMode);
}
if (AATime)
{
*AATime = ConvertTimestampToUnixSafe(ATime, SessionData->DSTMode);
}
if (AMTime)
{
*AMTime = ConvertTimestampToUnix(MTime, SessionData->DSTMode);
}
}
if (ASize)
{
// Get file size
FILE_OPERATION_LOOP_BEGIN
{
unsigned long LSize;
unsigned long HSize;
LSize = GetFileSize(Handle, &HSize);
if ((LSize == 0xFFFFFFFF) && (GetLastError() != NO_ERROR)) RaiseLastOSError();
*ASize = (__int64(HSize) << 32) + LSize;
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_GET_ATTRS, (FileName)));
}
if ((AHandle == NULL) || NoHandle)
{
CloseHandle(Handle);
Handle = NULL;
}
}
catch(...)
{
CloseHandle(Handle);
throw;
}
}
if (AAttrs) *AAttrs = Attrs;
if (AHandle) *AHandle = Handle;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::OpenLocalFile(
const UnicodeString & FileName, unsigned int Access, TLocalFileHandle & Handle, bool TryWriteReadOnly)
{
Handle.FileName = FileName;
OpenLocalFile(
FileName, Access, &Handle.Attrs, &Handle.Handle, NULL, &Handle.MTime, &Handle.ATime, &Handle.Size,
TryWriteReadOnly);
Handle.Modification = UnixToDateTime(Handle.MTime, SessionData->DSTMode);
Handle.Directory = FLAGSET(Handle.Attrs, faDirectory);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::AllowLocalFileTransfer(UnicodeString FileName,
const TCopyParamType * CopyParam, TFileOperationProgressType * OperationProgress)
{
bool Result = true;
// optimization
if (Log->Logging || !CopyParam->AllowAnyTransfer())
{
WIN32_FIND_DATA FindData;
HANDLE Handle;
FILE_OPERATION_LOOP_BEGIN
{
Handle = FindFirstFile(ApiPath(ExcludeTrailingBackslash(FileName)).c_str(), &FindData);
if (Handle == INVALID_HANDLE_VALUE)
{
RaiseLastOSError();
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
::FindClose(Handle);
bool Directory = FLAGSET(FindData.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY);
TFileMasks::TParams Params;
// SearchRec.Size in C++B2010 is __int64,
// so we should be able to use it instead of FindData.nFileSize*
Params.Size =
(static_cast<__int64>(FindData.nFileSizeHigh) << 32) +
FindData.nFileSizeLow;
Params.Modification = FileTimeToDateTime(FindData.ftLastWriteTime);
UnicodeString BaseFileName = GetBaseFileName(FileName);
if (!CopyParam->AllowTransfer(BaseFileName, osLocal, Directory, Params))
{
LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
Result = false;
}
else if (CopyParam->SkipTransfer(FileName, Directory))
{
OperationProgress->AddSkippedFileSize(Params.Size);
Result = false;
}
if (Result)
{
LogFileDetails(FileName, Params.Modification, Params.Size);
}
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::MakeLocalFileList(const UnicodeString FileName,
const TSearchRec Rec, void * Param)
{
TMakeLocalFileListParams & Params = *static_cast<TMakeLocalFileListParams*>(Param);
bool Directory = FLAGSET(Rec.Attr, faDirectory);
if (Directory && Params.Recursive)
{
ProcessLocalDirectory(FileName, MakeLocalFileList, &Params);
}
if (!Directory || Params.IncludeDirs)
{
Params.FileList->Add(FileName);
if (Params.FileTimes != NULL)
{
Params.FileTimes->push_back(const_cast<TSearchRec &>(Rec).TimeStamp);
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CalculateLocalFileSize(const UnicodeString FileName,
const TSearchRec Rec, /*TCalculateSizeParams*/ void * Params)
{
TCalculateSizeParams * AParams = static_cast<TCalculateSizeParams*>(Params);
if (!TryStartOperationWithFile(FileName, foCalculateSize))
{
AParams->Result = false;
}
else
{
try
{
bool Dir = FLAGSET(Rec.Attr, faDirectory);
bool AllowTransfer = (AParams->CopyParam == NULL);
// SearchRec.Size in C++B2010 is __int64,
// so we should be able to use it instead of FindData.nFileSize*
__int64 Size =
(static_cast<__int64>(Rec.FindData.nFileSizeHigh) << 32) +
Rec.FindData.nFileSizeLow;
if (!AllowTransfer)
{
TFileMasks::TParams MaskParams;
MaskParams.Size = Size;
MaskParams.Modification = FileTimeToDateTime(Rec.FindData.ftLastWriteTime);
UnicodeString BaseFileName = GetBaseFileName(FileName);
AllowTransfer = AParams->CopyParam->AllowTransfer(BaseFileName, osLocal, Dir, MaskParams);
}
if (AllowTransfer)
{
int CollectionIndex = -1;
if (AParams->Files != NULL)
{
UnicodeString FullFileName = ::ExpandFileName(FileName);
CollectionIndex = AParams->Files->Add(FullFileName, NULL, Dir);
}
if (!Dir)
{
AParams->Size += Size;
}
else
{
try
{
ProcessLocalDirectory(FileName, CalculateLocalFileSize, Params);
}
catch (...)
{
if (CollectionIndex >= 0)
{
AParams->Files->DidNotRecurse(CollectionIndex);
}
}
}
}
}
catch (...)
{
// ignore
}
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CalculateLocalFilesSize(TStrings * FileList,
__int64 & Size, const TCopyParamType * CopyParam, bool AllowDirs, TStrings * Files)
{
bool Result = false;
TFileOperationProgressType OperationProgress(&DoProgress, &DoFinished);
TOnceDoneOperation OnceDoneOperation = odoIdle;
OperationProgress.Start(foCalculateSize, osLocal, FileList->Count);
try
{
TCalculateSizeParams Params;
Params.Size = 0;
Params.Params = 0;
Params.CopyParam = CopyParam;
Params.Files = NULL;
Params.Result = true;
DebugAssert(!FOperationProgress);
FOperationProgress = &OperationProgress;
UnicodeString LastDirPath;
for (int Index = 0; Params.Result && (Index < FileList->Count); Index++)
{
UnicodeString FileName = FileList->Strings[Index];
TSearchRec Rec;
if (FileSearchRec(FileName, Rec))
{
bool Dir = FLAGSET(Rec.Attr, faDirectory);
if (Dir && !AllowDirs)
{
Params.Result = false;
}
else
{
if (Files != NULL)
{
UnicodeString FullFileName = ::ExpandFileName(FileName);
UnicodeString DirPath = ExtractFilePath(FullFileName);
if (DirPath != LastDirPath)
{
Params.Files = new TCollectedFileList();
LastDirPath = DirPath;
Files->AddObject(LastDirPath, Params.Files);
}
}
CalculateLocalFileSize(FileName, Rec, &Params);
OperationProgress.Finish(FileName, true, OnceDoneOperation);
}
}
}
Size = Params.Size;
Result = Params.Result;
}
__finally
{
FOperationProgress = NULL;
OperationProgress.Stop();
}
if (OnceDoneOperation != odoIdle)
{
CloseOnCompletion(OnceDoneOperation);
}
return Result;
}
//---------------------------------------------------------------------------
struct TSynchronizeFileData
{
bool Modified;
bool New;
bool IsDirectory;
TSynchronizeChecklist::TItem::TFileInfo Info;
TSynchronizeChecklist::TItem::TFileInfo MatchingRemoteFile;
TRemoteFile * MatchingRemoteFileFile;
int MatchingRemoteFileImageIndex;
FILETIME LocalLastWriteTime;
};
//---------------------------------------------------------------------------
const int sfFirstLevel = 0x01;
struct TSynchronizeData
{
UnicodeString LocalDirectory;
UnicodeString RemoteDirectory;
TTerminal::TSynchronizeMode Mode;
int Params;
TSynchronizeDirectory OnSynchronizeDirectory;
TSynchronizeOptions * Options;
int Flags;
TStringList * LocalFileList;
const TCopyParamType * CopyParam;
TSynchronizeChecklist * Checklist;
};
//---------------------------------------------------------------------------
TSynchronizeChecklist * __fastcall TTerminal::SynchronizeCollect(const UnicodeString LocalDirectory,
const UnicodeString RemoteDirectory, TSynchronizeMode Mode,
const TCopyParamType * CopyParam, int Params,
TSynchronizeDirectory OnSynchronizeDirectory,
TSynchronizeOptions * Options)
{
TValueRestorer<bool> UseBusyCursorRestorer(FUseBusyCursor);
FUseBusyCursor = false;
TSynchronizeChecklist * Checklist = new TSynchronizeChecklist();
try
{
DoSynchronizeCollectDirectory(LocalDirectory, RemoteDirectory, Mode,
CopyParam, Params, OnSynchronizeDirectory, Options, sfFirstLevel,
Checklist);
Checklist->Sort();
}
catch(...)
{
delete Checklist;
throw;
}
return Checklist;
}
//---------------------------------------------------------------------------
static void __fastcall AddFlagName(UnicodeString & ParamsStr, int & Params, int Param, const UnicodeString & Name)
{
if (FLAGSET(Params, Param))
{
AddToList(ParamsStr, Name, ", ");
}
Params &= ~Param;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::SynchronizeModeStr(TSynchronizeMode Mode)
{
UnicodeString ModeStr;
switch (Mode)
{
case smRemote:
ModeStr = L"Remote";
break;
case smLocal:
ModeStr = L"Local";
break;
case smBoth:
ModeStr = L"Both";
break;
default:
ModeStr = L"Unknown";
break;
}
return ModeStr;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::SynchronizeParamsStr(int Params)
{
UnicodeString ParamsStr;
AddFlagName(ParamsStr, Params, spDelete, L"Delete");
AddFlagName(ParamsStr, Params, spNoConfirmation, L"NoConfirmation");
AddFlagName(ParamsStr, Params, spExistingOnly, L"ExistingOnly");
AddFlagName(ParamsStr, Params, spNoRecurse, L"NoRecurse");
AddFlagName(ParamsStr, Params, spUseCache, L"UseCache");
AddFlagName(ParamsStr, Params, spDelayProgress, L"DelayProgress");
AddFlagName(ParamsStr, Params, spPreviewChanges, L"*PreviewChanges"); // GUI only
AddFlagName(ParamsStr, Params, spSubDirs, L"SubDirs");
AddFlagName(ParamsStr, Params, spTimestamp, L"Timestamp");
AddFlagName(ParamsStr, Params, spNotByTime, L"NotByTime");
AddFlagName(ParamsStr, Params, spBySize, L"BySize");
AddFlagName(ParamsStr, Params, spSelectedOnly, L"*SelectedOnly"); // GUI only
AddFlagName(ParamsStr, Params, spMirror, L"Mirror");
if (Params > 0)
{
AddToList(ParamsStr, FORMAT(L"0x%x", (int(Params))), L", ");
}
return ParamsStr;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoSynchronizeCollectDirectory(const UnicodeString LocalDirectory,
const UnicodeString RemoteDirectory, TSynchronizeMode Mode,
const TCopyParamType * CopyParam, int Params,
TSynchronizeDirectory OnSynchronizeDirectory, TSynchronizeOptions * Options,
int Flags, TSynchronizeChecklist * Checklist)
{
TSynchronizeData Data;
Data.LocalDirectory = IncludeTrailingBackslash(LocalDirectory);
Data.RemoteDirectory = UnixIncludeTrailingBackslash(RemoteDirectory);
Data.Mode = Mode;
Data.Params = Params;
Data.OnSynchronizeDirectory = OnSynchronizeDirectory;
Data.LocalFileList = NULL;
Data.CopyParam = CopyParam;
Data.Options = Options;
Data.Flags = Flags;
Data.Checklist = Checklist;
LogEvent(FORMAT(L"Collecting synchronization list for local directory '%s' and remote directory '%s', "
"mode = %s, params = 0x%x (%s), file mask = '%s'", (LocalDirectory, RemoteDirectory,
SynchronizeModeStr(Mode), int(Params), SynchronizeParamsStr(Params), CopyParam->IncludeFileMask.Masks)));
if (FLAGCLEAR(Params, spDelayProgress))
{
DoSynchronizeProgress(Data, true);
}
try
{
bool Found;
TSearchRecChecked SearchRec;
Data.LocalFileList = CreateSortedStringList();
FILE_OPERATION_LOOP_BEGIN
{
int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
Found = (FindFirstChecked(Data.LocalDirectory + L"*.*", FindAttrs, SearchRec) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(LIST_DIR_ERROR, (LocalDirectory)));
if (Found)
{
try
{
UnicodeString FileName;
while (Found)
{
FileName = SearchRec.Name;
// add dirs for recursive mode or when we are interested in newly
// added subdirs
// SearchRec.Size in C++B2010 is __int64,
// so we should be able to use it instead of FindData.nFileSize*
__int64 Size =
(static_cast<__int64>(SearchRec.FindData.nFileSizeHigh) << 32) +
SearchRec.FindData.nFileSizeLow;
TDateTime Modification = FileTimeToDateTime(SearchRec.FindData.ftLastWriteTime);
TFileMasks::TParams MaskParams;
MaskParams.Size = Size;
MaskParams.Modification = Modification;
UnicodeString RemoteFileName =
ChangeFileName(CopyParam, FileName, osLocal, false);
UnicodeString FullLocalFileName = Data.LocalDirectory + FileName;
UnicodeString BaseFileName = GetBaseFileName(FullLocalFileName);
if ((FileName != L".") && (FileName != L"..") &&
CopyParam->AllowTransfer(BaseFileName, osLocal,
FLAGSET(SearchRec.Attr, faDirectory), MaskParams) &&
!FFileSystem->TemporaryTransferFile(FileName) &&
(FLAGCLEAR(Flags, sfFirstLevel) ||
(Options == NULL) ||
Options->MatchesFilter(FileName) ||
Options->MatchesFilter(RemoteFileName)))
{
TSynchronizeFileData * FileData = new TSynchronizeFileData;
FileData->IsDirectory = FLAGSET(SearchRec.Attr, faDirectory);
FileData->Info.FileName = FileName;
FileData->Info.Directory = Data.LocalDirectory;
FileData->Info.Modification = Modification;
FileData->Info.ModificationFmt = mfFull;
FileData->Info.Size = Size;
FileData->LocalLastWriteTime = SearchRec.FindData.ftLastWriteTime;
FileData->New = true;
FileData->Modified = false;
Data.LocalFileList->AddObject(FileName,
reinterpret_cast<TObject*>(FileData));
if (Configuration->ActualLogProtocol >= 0)
{
LogEvent(FORMAT(L"Local file %s included to synchronization",
(FormatFileDetailsForLog(FullLocalFileName, Modification, Size))));
}
}
else
{
if (Configuration->ActualLogProtocol >= 0)
{
LogEvent(FORMAT(L"Local file %s excluded from synchronization",
(FormatFileDetailsForLog(FullLocalFileName, Modification, Size))));
}
}
FILE_OPERATION_LOOP_BEGIN
{
Found = (FindNextChecked(SearchRec) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(LIST_DIR_ERROR, (LocalDirectory)));
}
}
__finally
{
FindClose(SearchRec);
}
// can we expect that ProcessDirectory would take so little time
// that we can postpone showing progress window until anything actually happens?
bool Cached = FLAGSET(Params, spUseCache) && SessionData->CacheDirectories &&
FDirectoryCache->HasFileList(RemoteDirectory);
if (!Cached && FLAGSET(Params, spDelayProgress))
{
DoSynchronizeProgress(Data, true);
}
ProcessDirectory(RemoteDirectory, SynchronizeCollectFile, &Data,
FLAGSET(Params, spUseCache));
TSynchronizeFileData * FileData;
for (int Index = 0; Index < Data.LocalFileList->Count; Index++)
{
FileData = reinterpret_cast<TSynchronizeFileData *>
(Data.LocalFileList->Objects[Index]);
// add local file either if we are going to upload it
// (i.e. if it is updated or we want to upload even new files)
// or if we are going to delete it (i.e. all "new"=obsolete files)
bool Modified = (FileData->Modified && ((Mode == smBoth) || (Mode == smRemote)));
bool New = (FileData->New &&
((Mode == smLocal) ||
(((Mode == smBoth) || (Mode == smRemote)) && FLAGCLEAR(Params, spTimestamp))));
if (New)
{
LogEvent(FORMAT(L"Local file %s is new",
(FormatFileDetailsForLog(FileData->Info.Directory + FileData->Info.FileName,
FileData->Info.Modification, FileData->Info.Size))));
}
if (Modified || New)
{
TSynchronizeChecklist::TItem * ChecklistItem = new TSynchronizeChecklist::TItem();
try
{
ChecklistItem->IsDirectory = FileData->IsDirectory;
ChecklistItem->Local = FileData->Info;
ChecklistItem->FLocalLastWriteTime = FileData->LocalLastWriteTime;
if (Modified)
{
DebugAssert(!FileData->MatchingRemoteFile.Directory.IsEmpty());
ChecklistItem->Remote = FileData->MatchingRemoteFile;
ChecklistItem->ImageIndex = FileData->MatchingRemoteFileImageIndex;
ChecklistItem->RemoteFile = FileData->MatchingRemoteFileFile;
}
else
{
ChecklistItem->Remote.Directory = Data.RemoteDirectory;
}
if ((Mode == smBoth) || (Mode == smRemote))
{
ChecklistItem->Action =
(Modified ? TSynchronizeChecklist::saUploadUpdate : TSynchronizeChecklist::saUploadNew);
ChecklistItem->Checked =
(Modified || FLAGCLEAR(Params, spExistingOnly)) &&
(!ChecklistItem->IsDirectory || FLAGCLEAR(Params, spNoRecurse) ||
FLAGSET(Params, spSubDirs));
}
else if ((Mode == smLocal) && FLAGCLEAR(Params, spTimestamp))
{
ChecklistItem->Action = TSynchronizeChecklist::saDeleteLocal;
ChecklistItem->Checked =
FLAGSET(Params, spDelete) &&
(!ChecklistItem->IsDirectory || FLAGCLEAR(Params, spNoRecurse) ||
FLAGSET(Params, spSubDirs));
}
if (ChecklistItem->Action != TSynchronizeChecklist::saNone)
{
Data.Checklist->Add(ChecklistItem);
ChecklistItem = NULL;
}
}
__finally
{
delete ChecklistItem;
}
}
else
{
if (FileData->Modified)
{
delete FileData->MatchingRemoteFileFile;
}
}
}
}
}
__finally
{
if (Data.LocalFileList != NULL)
{
for (int Index = 0; Index < Data.LocalFileList->Count; Index++)
{
TSynchronizeFileData * FileData = reinterpret_cast<TSynchronizeFileData*>
(Data.LocalFileList->Objects[Index]);
delete FileData;
}
delete Data.LocalFileList;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SynchronizeCollectFile(const UnicodeString FileName,
const TRemoteFile * File, /*TSynchronizeData*/ void * Param)
{
try
{
DoSynchronizeCollectFile(FileName, File, Param);
}
catch(ESkipFile & E)
{
TSuspendFileOperationProgress Suspend(OperationProgress);
if (!HandleException(&E))
{
throw;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoSynchronizeCollectFile(const UnicodeString FileName,
const TRemoteFile * File, /*TSynchronizeData*/ void * Param)
{
TSynchronizeData * Data = static_cast<TSynchronizeData *>(Param);
TFileMasks::TParams MaskParams;
MaskParams.Size = File->Size;
MaskParams.Modification = File->Modification;
UnicodeString LocalFileName =
ChangeFileName(Data->CopyParam, File->FileName, osRemote, false);
UnicodeString FullRemoteFileName =
UnixExcludeTrailingBackslash(File->FullFileName);
UnicodeString BaseFileName = GetBaseFileName(FullRemoteFileName);
if (Data->CopyParam->AllowTransfer(
BaseFileName, osRemote,
File->IsDirectory, MaskParams) &&
!FFileSystem->TemporaryTransferFile(File->FileName) &&
(FLAGCLEAR(Data->Flags, sfFirstLevel) ||
(Data->Options == NULL) ||
Data->Options->MatchesFilter(File->FileName) ||
Data->Options->MatchesFilter(LocalFileName)))
{
TSynchronizeChecklist::TItem * ChecklistItem = new TSynchronizeChecklist::TItem();
try
{
ChecklistItem->IsDirectory = File->IsDirectory;
ChecklistItem->ImageIndex = File->IconIndex;
ChecklistItem->Remote.FileName = File->FileName;
ChecklistItem->Remote.Directory = Data->RemoteDirectory;
ChecklistItem->Remote.Modification = File->Modification;
ChecklistItem->Remote.ModificationFmt = File->ModificationFmt;
ChecklistItem->Remote.Size = File->Size;
bool Modified = false;
bool New = false;
if (File->IsDirectory && !CanRecurseToDirectory(File))
{
LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (File->FileName)));
}
else
{
int LocalIndex = Data->LocalFileList->IndexOf(LocalFileName);
New = (LocalIndex < 0);
if (!New)
{
TSynchronizeFileData * LocalData =
reinterpret_cast<TSynchronizeFileData *>(Data->LocalFileList->Objects[LocalIndex]);
LocalData->New = false;
if (File->IsDirectory != LocalData->IsDirectory)
{
LogEvent(FORMAT(L"%s is directory on one side, but file on the another",
(File->FileName)));
}
else if (!File->IsDirectory)
{
ChecklistItem->Local = LocalData->Info;
ChecklistItem->Local.Modification =
ReduceDateTimePrecision(ChecklistItem->Local.Modification, File->ModificationFmt);
bool LocalModified = false;
// for spTimestamp+spBySize require that the file sizes are the same
// before comparing file time
int TimeCompare;
if (FLAGCLEAR(Data->Params, spNotByTime) &&
(FLAGCLEAR(Data->Params, spTimestamp) ||
FLAGCLEAR(Data->Params, spBySize) ||
(ChecklistItem->Local.Size == ChecklistItem->Remote.Size)))
{
TimeCompare = CompareFileTime(ChecklistItem->Local.Modification,
ChecklistItem->Remote.Modification);
}
else
{
TimeCompare = 0;
}
if (TimeCompare < 0)
{
if ((FLAGCLEAR(Data->Params, spTimestamp) && FLAGCLEAR(Data->Params, spMirror)) ||
(Data->Mode == smBoth) || (Data->Mode == smLocal))
{
Modified = true;
}
else
{
LocalModified = true;
}
}
else if (TimeCompare > 0)
{
if ((FLAGCLEAR(Data->Params, spTimestamp) && FLAGCLEAR(Data->Params, spMirror)) ||
(Data->Mode == smBoth) || (Data->Mode == smRemote))
{
LocalModified = true;
}
else
{
Modified = true;
}
}
else if (FLAGSET(Data->Params, spBySize) &&
(ChecklistItem->Local.Size != ChecklistItem->Remote.Size) &&
FLAGCLEAR(Data->Params, spTimestamp))
{
Modified = true;
LocalModified = true;
}
if (LocalModified)
{
LocalData->Modified = true;
LocalData->MatchingRemoteFile = ChecklistItem->Remote;
LocalData->MatchingRemoteFileImageIndex = ChecklistItem->ImageIndex;
// we need this for custom commands over checklist only,
// not for sync itself
LocalData->MatchingRemoteFileFile = File->Duplicate();
LogEvent(FORMAT(L"Local file %s is modified comparing to remote file %s",
(FormatFileDetailsForLog(LocalData->Info.Directory + LocalData->Info.FileName,
LocalData->Info.Modification, LocalData->Info.Size),
FormatFileDetailsForLog(FullRemoteFileName,
File->Modification, File->Size))));
}
if (Modified)
{
LogEvent(FORMAT(L"Remote file %s is modified comparing to local file %s",
(FormatFileDetailsForLog(FullRemoteFileName, File->Modification, File->Size),
FormatFileDetailsForLog(LocalData->Info.Directory + LocalData->Info.FileName,
LocalData->Info.Modification, LocalData->Info.Size))));
}
}
else if (FLAGCLEAR(Data->Params, spNoRecurse))
{
DoSynchronizeCollectDirectory(
Data->LocalDirectory + LocalData->Info.FileName,
Data->RemoteDirectory + File->FileName,
Data->Mode, Data->CopyParam, Data->Params, Data->OnSynchronizeDirectory,
Data->Options, (Data->Flags & ~sfFirstLevel),
Data->Checklist);
}
}
else
{
ChecklistItem->Local.Directory = Data->LocalDirectory;
LogEvent(FORMAT(L"Remote file %s is new",
(FormatFileDetailsForLog(FullRemoteFileName, File->Modification, File->Size))));
}
}
if (New || Modified)
{
DebugAssert(!New || !Modified);
// download the file if it changed or is new and we want to have it locally
if ((Data->Mode == smBoth) || (Data->Mode == smLocal))
{
if (FLAGCLEAR(Data->Params, spTimestamp) || Modified)
{
ChecklistItem->Action =
(Modified ? TSynchronizeChecklist::saDownloadUpdate : TSynchronizeChecklist::saDownloadNew);
ChecklistItem->Checked =
(Modified || FLAGCLEAR(Data->Params, spExistingOnly)) &&
(!ChecklistItem->IsDirectory || FLAGCLEAR(Data->Params, spNoRecurse) ||
FLAGSET(Data->Params, spSubDirs));
}
}
else if ((Data->Mode == smRemote) && New)
{
if (FLAGCLEAR(Data->Params, spTimestamp))
{
ChecklistItem->Action = TSynchronizeChecklist::saDeleteRemote;
ChecklistItem->Checked =
FLAGSET(Data->Params, spDelete) &&
(!ChecklistItem->IsDirectory || FLAGCLEAR(Data->Params, spNoRecurse) ||
FLAGSET(Data->Params, spSubDirs));
}
}
if (ChecklistItem->Action != TSynchronizeChecklist::saNone)
{
ChecklistItem->RemoteFile = File->Duplicate();
Data->Checklist->Add(ChecklistItem);
ChecklistItem = NULL;
}
}
}
__finally
{
delete ChecklistItem;
}
}
else
{
if (Configuration->ActualLogProtocol >= 0)
{
LogEvent(FORMAT(L"Remote file %s excluded from synchronization",
(FormatFileDetailsForLog(FullRemoteFileName, File->Modification, File->Size))));
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SynchronizeApply(TSynchronizeChecklist * Checklist,
const UnicodeString LocalDirectory, const UnicodeString RemoteDirectory,
const TCopyParamType * CopyParam, int Params,
TSynchronizeDirectory OnSynchronizeDirectory)
{
TSynchronizeData Data;
Data.OnSynchronizeDirectory = OnSynchronizeDirectory;
int CopyParams =
FLAGMASK(FLAGSET(Params, spNoConfirmation), cpNoConfirmation);
TCopyParamType SyncCopyParam = *CopyParam;
// when synchronizing by time, we force preserving time,
// otherwise it does not make any sense
if (FLAGCLEAR(Params, spNotByTime))
{
SyncCopyParam.PreserveTime = true;
}
TStringList * DownloadList = new TStringList();
TStringList * DeleteRemoteList = new TStringList();
TStringList * UploadList = new TStringList();
TStringList * DeleteLocalList = new TStringList();
BeginTransaction();
try
{
int IIndex = 0;
while (IIndex < Checklist->Count)
{
const TSynchronizeChecklist::TItem * ChecklistItem;
DownloadList->Clear();
DeleteRemoteList->Clear();
UploadList->Clear();
DeleteLocalList->Clear();
ChecklistItem = Checklist->Item[IIndex];
UnicodeString CurrentLocalDirectory = ChecklistItem->Local.Directory;
UnicodeString CurrentRemoteDirectory = ChecklistItem->Remote.Directory;
LogEvent(FORMAT(L"Synchronizing local directory '%s' with remote directory '%s', "
"params = 0x%x (%s)", (CurrentLocalDirectory, CurrentRemoteDirectory,
int(Params), SynchronizeParamsStr(Params))));
int Count = 0;
while ((IIndex < Checklist->Count) &&
(Checklist->Item[IIndex]->Local.Directory == CurrentLocalDirectory) &&
(Checklist->Item[IIndex]->Remote.Directory == CurrentRemoteDirectory))
{
ChecklistItem = Checklist->Item[IIndex];
if (ChecklistItem->Checked)
{
Count++;
if (FLAGSET(Params, spTimestamp))
{
switch (ChecklistItem->Action)
{
case TSynchronizeChecklist::saDownloadUpdate:
DownloadList->AddObject(
UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) +
ChecklistItem->Remote.FileName,
(TObject *)(ChecklistItem));
break;
case TSynchronizeChecklist::saUploadUpdate:
UploadList->AddObject(
IncludeTrailingBackslash(ChecklistItem->Local.Directory) +
ChecklistItem->Local.FileName,
(TObject *)(ChecklistItem));
break;
default:
DebugFail();
break;
}
}
else
{
switch (ChecklistItem->Action)
{
case TSynchronizeChecklist::saDownloadNew:
case TSynchronizeChecklist::saDownloadUpdate:
DownloadList->AddObject(
UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) +
ChecklistItem->Remote.FileName,
ChecklistItem->RemoteFile);
break;
case TSynchronizeChecklist::saDeleteRemote:
DeleteRemoteList->AddObject(
UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) +
ChecklistItem->Remote.FileName,
ChecklistItem->RemoteFile);
break;
case TSynchronizeChecklist::saUploadNew:
case TSynchronizeChecklist::saUploadUpdate:
UploadList->Add(
IncludeTrailingBackslash(ChecklistItem->Local.Directory) +
ChecklistItem->Local.FileName);
break;
case TSynchronizeChecklist::saDeleteLocal:
DeleteLocalList->Add(
IncludeTrailingBackslash(ChecklistItem->Local.Directory) +
ChecklistItem->Local.FileName);
break;
default:
DebugFail();
break;
}
}
}
IIndex++;
}
// prevent showing/updating of progress dialog if there's nothing to do
if (Count > 0)
{
Data.LocalDirectory = IncludeTrailingBackslash(CurrentLocalDirectory);
Data.RemoteDirectory = UnixIncludeTrailingBackslash(CurrentRemoteDirectory);
DoSynchronizeProgress(Data, false);
if (FLAGSET(Params, spTimestamp))
{
if (DownloadList->Count > 0)
{
ProcessFiles(DownloadList, foSetProperties,
SynchronizeLocalTimestamp, NULL, osLocal);
}
if (UploadList->Count > 0)
{
ProcessFiles(UploadList, foSetProperties,
SynchronizeRemoteTimestamp);
}
}
else
{
if ((DownloadList->Count > 0) &&
!CopyToLocal(DownloadList, Data.LocalDirectory, &SyncCopyParam, CopyParams, NULL))
{
Abort();
}
if ((DeleteRemoteList->Count > 0) &&
!DeleteFiles(DeleteRemoteList))
{
Abort();
}
if ((UploadList->Count > 0) &&
!CopyToRemote(UploadList, Data.RemoteDirectory, &SyncCopyParam, CopyParams, NULL))
{
Abort();
}
if ((DeleteLocalList->Count > 0) &&
!DeleteLocalFiles(DeleteLocalList))
{
Abort();
}
}
}
}
}
__finally
{
delete DownloadList;
delete DeleteRemoteList;
delete UploadList;
delete DeleteLocalList;
EndTransaction();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoSynchronizeProgress(const TSynchronizeData & Data,
bool Collect)
{
if (Data.OnSynchronizeDirectory != NULL)
{
bool Continue = true;
Data.OnSynchronizeDirectory(Data.LocalDirectory, Data.RemoteDirectory,
Continue, Collect);
if (!Continue)
{
Abort();
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SynchronizeLocalTimestamp(const UnicodeString /*FileName*/,
const TRemoteFile * File, void * /*Param*/)
{
const TSynchronizeChecklist::TItem * ChecklistItem =
reinterpret_cast<const TSynchronizeChecklist::TItem *>(File);
UnicodeString LocalFile =
IncludeTrailingBackslash(ChecklistItem->Local.Directory) +
ChecklistItem->Local.FileName;
FILE_OPERATION_LOOP_BEGIN
{
HANDLE Handle;
OpenLocalFile(LocalFile, GENERIC_WRITE, NULL, &Handle,
NULL, NULL, NULL, NULL);
FILETIME WrTime = DateTimeToFileTime(ChecklistItem->Remote.Modification,
SessionData->DSTMode);
bool Result = SetFileTime(Handle, NULL, NULL, &WrTime);
int Error = GetLastError();
CloseHandle(Handle);
if (!Result)
{
RaiseLastOSError(Error);
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (LocalFile)));
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SynchronizeRemoteTimestamp(const UnicodeString /*FileName*/,
const TRemoteFile * File, void * /*Param*/)
{
const TSynchronizeChecklist::TItem * ChecklistItem =
reinterpret_cast<const TSynchronizeChecklist::TItem *>(File);
TRemoteProperties Properties;
Properties.Valid << vpModification;
Properties.Modification = ConvertTimestampToUnix(ChecklistItem->FLocalLastWriteTime,
SessionData->DSTMode);
ChangeFileProperties(
UnixIncludeTrailingBackslash(ChecklistItem->Remote.Directory) + ChecklistItem->Remote.FileName,
NULL, &Properties);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FileFind(UnicodeString FileName,
const TRemoteFile * File, /*TFilesFindParams*/ void * Param)
{
// see DoFilesFind
FOnFindingFile = NULL;
DebugAssert(Param);
DebugAssert(File);
TFilesFindParams * AParams = static_cast<TFilesFindParams*>(Param);
if (!AParams->Cancel)
{
if (FileName.IsEmpty())
{
FileName = File->FileName;
}
TFileMasks::TParams MaskParams;
MaskParams.Size = File->Size;
MaskParams.Modification = File->Modification;
UnicodeString FullFileName = UnixExcludeTrailingBackslash(File->FullFileName);
bool ImplicitMatch;
// Do not use recursive include match
if (AParams->FileMask.Matches(FullFileName, false,
File->IsDirectory, &MaskParams, false, ImplicitMatch))
{
if (!ImplicitMatch)
{
AParams->OnFileFound(this, FileName, File, AParams->Cancel);
}
if (File->IsDirectory)
{
UnicodeString RealDirectory;
if (!File->IsSymLink || File->LinkTo.IsEmpty())
{
RealDirectory = UnixIncludeTrailingBackslash(AParams->RealDirectory) + File->FileName;
}
else
{
RealDirectory = ::AbsolutePath(AParams->RealDirectory, File->LinkTo);
}
if (!AParams->LoopDetector.IsUnvisitedDirectory(RealDirectory))
{
LogEvent(FORMAT(L"Already searched \"%s\" directory (real path \"%s\"), link loop detected", (FullFileName, RealDirectory)));
}
else
{
DoFilesFind(FullFileName, *AParams, RealDirectory);
}
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoFilesFind(UnicodeString Directory, TFilesFindParams & Params, UnicodeString RealDirectory)
{
LogEvent(FORMAT(L"Searching directory \"%s\" (real path \"%s\")", (Directory, RealDirectory)));
Params.OnFindingFile(this, Directory, Params.Cancel);
if (!Params.Cancel)
{
DebugAssert(FOnFindingFile == NULL);
// ideally we should set the handler only around actually reading
// of the directory listing, so we at least reset the handler in
// FileFind
FOnFindingFile = Params.OnFindingFile;
UnicodeString PrevRealDirectory = Params.RealDirectory;
try
{
Params.RealDirectory = RealDirectory;
ProcessDirectory(Directory, FileFind, &Params, false, true);
}
__finally
{
Params.RealDirectory = PrevRealDirectory;
FOnFindingFile = NULL;
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::FilesFind(UnicodeString Directory, const TFileMasks & FileMask,
TFileFoundEvent OnFileFound, TFindingFileEvent OnFindingFile)
{
TFilesFindParams Params;
Params.FileMask = FileMask;
Params.OnFileFound = OnFileFound;
Params.OnFindingFile = OnFindingFile;
Params.Cancel = false;
Params.LoopDetector.RecordVisitedDirectory(Directory);
DoFilesFind(Directory, Params, Directory);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SpaceAvailable(const UnicodeString Path,
TSpaceAvailable & ASpaceAvailable)
{
DebugAssert(IsCapable[fcCheckingSpaceAvailable]);
try
{
FFileSystem->SpaceAvailable(Path, ASpaceAvailable);
}
catch (Exception &E)
{
CommandError(&E, FMTLOAD(SPACE_AVAILABLE_ERROR, (Path)));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LockFile(const UnicodeString FileName,
const TRemoteFile * File, void * /*Param*/)
{
StartOperationWithFile(FileName, foLock);
LogEvent(FORMAT(L"Locking file \"%s\".", (FileName)));
FileModified(File, FileName, true);
DoLockFile(FileName, File);
ReactOnCommand(fsLock);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoLockFile(const UnicodeString & FileName, const TRemoteFile * File)
{
TRetryOperationLoop RetryLoop(this);
do
{
try
{
FFileSystem->LockFile(FileName, File);
}
catch(Exception & E)
{
RetryLoop.Error(E, FMTLOAD(LOCK_FILE_ERROR, (FileName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::UnlockFile(const UnicodeString FileName,
const TRemoteFile * File, void * /*Param*/)
{
StartOperationWithFile(FileName, foUnlock);
LogEvent(FORMAT(L"Unlocking file \"%s\".", (FileName)));
FileModified(File, FileName, true);
DoUnlockFile(FileName, File);
ReactOnCommand(fsLock);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoUnlockFile(const UnicodeString & FileName, const TRemoteFile * File)
{
TRetryOperationLoop RetryLoop(this);
do
{
try
{
FFileSystem->UnlockFile(FileName, File);
}
catch(Exception & E)
{
RetryLoop.Error(E, FMTLOAD(UNLOCK_FILE_ERROR, (FileName)));
}
}
while (RetryLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LockFiles(TStrings * FileList)
{
BeginTransaction();
try
{
ProcessFiles(FileList, foLock, LockFile, NULL);
}
__finally
{
EndTransaction();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::UnlockFiles(TStrings * FileList)
{
BeginTransaction();
try
{
ProcessFiles(FileList, foUnlock, UnlockFile, NULL);
}
__finally
{
EndTransaction();
}
}
//---------------------------------------------------------------------------
const TSessionInfo & __fastcall TTerminal::GetSessionInfo()
{
return FFileSystem->GetSessionInfo();
}
//---------------------------------------------------------------------------
const TFileSystemInfo & __fastcall TTerminal::GetFileSystemInfo(bool Retrieve)
{
return FFileSystem->GetFileSystemInfo(Retrieve);
}
//---------------------------------------------------------------------
void __fastcall TTerminal::GetSupportedChecksumAlgs(TStrings * Algs)
{
FFileSystem->GetSupportedChecksumAlgs(Algs);
}
//---------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetPassword()
{
UnicodeString Result;
// FRememberedPassword is empty also when stored password was used
if (FRememberedPassword.IsEmpty())
{
Result = SessionData->Password;
}
else
{
Result = RememberedPassword;
}
return Result;
}
//---------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetRememberedPassword()
{
return DecryptPassword(FRememberedPassword);
}
//---------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetRememberedTunnelPassword()
{
return DecryptPassword(FRememberedTunnelPassword);
}
//---------------------------------------------------------------------
bool __fastcall TTerminal::GetStoredCredentialsTried()
{
bool Result;
if (FFileSystem != NULL)
{
Result = FFileSystem->GetStoredCredentialsTried();
}
else if (FSecureShell != NULL)
{
Result = FSecureShell->GetStoredCredentialsTried();
}
else
{
DebugAssert(FTunnelOpening);
Result = false;
}
return Result;
}
//---------------------------------------------------------------------------
int __fastcall TTerminal::CopyToParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress)
{
UnicodeString FileName;
TObject * Object;
UnicodeString TargetDir;
bool Dir;
bool Recursed;
int Result = ParallelOperation->GetNext(this, FileName, Object, TargetDir, Dir, Recursed);
if (Result > 0)
{
std::unique_ptr<TStrings> FilesToCopy(new TStringList());
FilesToCopy->AddObject(FileName, Object);
if (ParallelOperation->Side == osLocal)
{
TargetDir = TranslateLockedPath(TargetDir, false);
}
// OnceDoneOperation is not supported
TOnceDoneOperation OnceDoneOperation = odoIdle;
int Params = ParallelOperation->Params;
// If we failed to recurse the directory when enumerating, recurse now (typically only to show the recursion error)
if (Dir && Recursed)
{
Params = Params | cpNoRecurse;
}
int Prev = OperationProgress->FilesFinishedSuccessfully;
try
{
FOperationProgress = OperationProgress;
if (ParallelOperation->Side == osLocal)
{
FFileSystem->CopyToRemote(
FilesToCopy.get(), TargetDir, ParallelOperation->CopyParam, Params, OperationProgress, OnceDoneOperation);
}
else if (DebugAlwaysTrue(ParallelOperation->Side == osRemote))
{
FFileSystem->CopyToLocal(
FilesToCopy.get(), TargetDir, ParallelOperation->CopyParam, Params, OperationProgress, OnceDoneOperation);
}
}
__finally
{
bool Success = (Prev < OperationProgress->FilesFinishedSuccessfully);
ParallelOperation->Done(FileName, Dir, Success);
FOperationProgress = NULL;
}
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CanParallel(
const TCopyParamType * CopyParam, int Params, TParallelOperation * ParallelOperation)
{
return
(ParallelOperation != NULL) &&
FFileSystem->IsCapable(fsParallelTransfers) &&
// parallel transfer is not implemented for operations needed to be done on a folder
// after all its files are processed
FLAGCLEAR(Params, cpDelete) &&
(!CopyParam->PreserveTime || !CopyParam->PreserveTimeDirs);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CopyParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress)
{
try
{
bool Continue = true;
do
{
int GotNext = CopyToParallel(ParallelOperation, OperationProgress);
if (GotNext < 0)
{
Continue = false;
}
else if (GotNext == 0)
{
Sleep(100);
}
}
while (Continue && !OperationProgress->Cancel);
}
__finally
{
OperationProgress->SetDone();
ParallelOperation->WaitFor();
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LogParallelTransfer(TParallelOperation * ParallelOperation)
{
LogEvent(
FORMAT("Adding a parallel transfer to the transfer started on the connection \"%s\"",
(ParallelOperation->MainName)));
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LogTotalTransferDetails(
const UnicodeString TargetDir, const TCopyParamType * CopyParam,
TFileOperationProgressType * OperationProgress, bool Parallel, TStrings * Files)
{
if (Log->Logging)
{
UnicodeString TargetSide = ((OperationProgress->Side == osLocal) ? L"remote" : L"local");
UnicodeString S =
FORMAT(
L"Copying %d files/directories to %s directory \"%s\"",
(OperationProgress->Count, TargetSide, TargetDir));
if (Parallel && DebugAlwaysTrue(Files != NULL))
{
int Count = 0;
for (int Index = 0; Index < Files->Count; Index++)
{
TCollectedFileList * FileList = dynamic_cast<TCollectedFileList *>(Files->Objects[Index]);
Count += FileList->Count();
}
S += FORMAT(L" - in parallel, with %d total files", (Count));
}
if (OperationProgress->TotalSizeSet)
{
S += FORMAT(L" - total size: %s", (FormatSize(OperationProgress->TotalSize)));
}
LogEvent(S);
if (Configuration->ActualLogProtocol >= 0)
{
LogEvent(CopyParam->LogStr);
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::LogTotalTransferDone(TFileOperationProgressType * OperationProgress)
{
LogEvent(L"Copying finished: " + OperationProgress->GetLogStr(true));
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CopyToRemote(TStrings * FilesToCopy,
const UnicodeString TargetDir, const TCopyParamType * CopyParam, int Params, TParallelOperation * ParallelOperation)
{
DebugAssert(FFileSystem);
DebugAssert(FilesToCopy);
bool Result = false;
TOnceDoneOperation OnceDoneOperation = odoIdle;
try
{
__int64 Size;
std::unique_ptr<TStringList> Files;
if (CanParallel(CopyParam, Params, ParallelOperation) &&
!CopyParam->ClearArchive)
{
Files.reset(new TStringList());
Files->OwnsObjects = true;
}
// dirty trick: when moving, do not pass copy param to avoid exclude mask
bool CalculatedSize =
CalculateLocalFilesSize(
FilesToCopy, Size,
(FLAGCLEAR(Params, cpDelete) ? CopyParam : NULL),
CopyParam->CalculateSize, Files.get());
FLastProgressLogged = GetTickCount();
TFileOperationProgressType OperationProgress(&DoProgress, &DoFinished);
OperationProgress.Start((Params & cpDelete ? foMove : foCopy), osLocal,
FilesToCopy->Count, Params & cpTemporary, TargetDir, CopyParam->CPSLimit);
FOperationProgress = &OperationProgress;
bool CollectingUsage = false;
try
{
if (CalculatedSize)
{
if (Configuration->Usage->Collect)
{
int CounterSize = TUsage::CalculateCounterSize(Size);
Configuration->Usage->Inc(L"Uploads");
Configuration->Usage->Inc(L"UploadedBytes", CounterSize);
Configuration->Usage->SetMax(L"MaxUploadSize", CounterSize);
CollectingUsage = true;
}
OperationProgress.SetTotalSize(Size);
}
UnicodeString UnlockedTargetDir = TranslateLockedPath(TargetDir, false);
BeginTransaction();
try
{
bool Parallel = CalculatedSize && (Files.get() != NULL);
LogTotalTransferDetails(TargetDir, CopyParam, &OperationProgress, Parallel, Files.get());
if (Parallel)
{
// OnceDoneOperation is not supported
ParallelOperation->Init(Files.release(), UnlockedTargetDir, CopyParam, Params, &OperationProgress, Log->Name);
CopyParallel(ParallelOperation, &OperationProgress);
}
else
{
FFileSystem->CopyToRemote(FilesToCopy, UnlockedTargetDir,
CopyParam, Params, &OperationProgress, OnceDoneOperation);
}
LogTotalTransferDone(&OperationProgress);
}
__finally
{
if (Active)
{
ReactOnCommand(fsCopyToRemote);
}
EndTransaction();
}
if (OperationProgress.Cancel == csContinue)
{
Result = true;
}
}
__finally
{
if (CollectingUsage)
{
int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed());
Configuration->Usage->Inc(L"UploadTime", CounterTime);
Configuration->Usage->SetMax(L"MaxUploadTime", CounterTime);
}
OperationProgress.Stop();
FOperationProgress = NULL;
}
}
catch (Exception &E)
{
CommandError(&E, MainInstructions(LoadStr(TOREMOTE_COPY_ERROR)));
OnceDoneOperation = odoIdle;
}
if (OnceDoneOperation != odoIdle)
{
CloseOnCompletion(OnceDoneOperation);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCopyToRemote(
TStrings * FilesToCopy, const UnicodeString & ATargetDir, const TCopyParamType * CopyParam, int Params,
TFileOperationProgressType * OperationProgress, unsigned int Flags, TOnceDoneOperation & OnceDoneOperation)
{
DebugAssert((FilesToCopy != NULL) && (OperationProgress != NULL));
FFileSystem->TransferOnDirectory(ATargetDir, CopyParam, Params);
UnicodeString TargetDir = AbsolutePath(ATargetDir, false);
UnicodeString FullTargetDir = UnixIncludeTrailingBackslash(TargetDir);
int Index = 0;
while ((Index < FilesToCopy->Count) && !OperationProgress->Cancel)
{
bool Success = false;
UnicodeString FileName = FilesToCopy->Strings[Index];
try
{
try
{
if (SessionData->CacheDirectories)
{
DirectoryModified(TargetDir, false);
if (DirectoryExists(ApiPath(FileName)))
{
UnicodeString FileNameOnly = ExtractFileName(FileName);
DirectoryModified(FullTargetDir + FileNameOnly, true);
}
}
SourceRobust(FileName, FullTargetDir, CopyParam, Params, OperationProgress, Flags | tfFirstLevel);
Success = true;
}
catch (ESkipFile & E)
{
TSuspendFileOperationProgress Suspend(OperationProgress);
if (!HandleException(&E))
{
throw;
}
}
}
__finally
{
OperationProgress->Finish(FileName, Success, OnceDoneOperation);
}
Index++;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SourceRobust(
const UnicodeString & FileName, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
TFileOperationProgressType * OperationProgress, unsigned int Flags)
{
TUploadSessionAction Action(ActionLog);
bool * AFileTransferAny = FLAGSET(Flags, tfUseFileTransferAny) ? &FFileTransferAny : NULL;
TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny);
do
{
bool ChildError = false;
try
{
Source(FileName, TargetDir, CopyParam, Params, OperationProgress, Flags, Action, ChildError);
}
catch (Exception & E)
{
if (!RobustLoop.TryReopen(E))
{
if (!ChildError)
{
RollbackAction(Action, OperationProgress, &E);
}
throw;
}
}
if (RobustLoop.ShouldRetry())
{
OperationProgress->RollbackTransfer();
Action.Restart();
// prevent overwrite and resume confirmations (should not be set for directories!)
Params |= cpNoConfirmation;
// enable resume even if we are uploading into new directory
Flags &= ~tfNewDirectory;
Flags |= tfAutoResume;
}
}
while (RobustLoop.Retry());
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CreateTargetDirectory(
const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam)
{
TRemoteProperties Properties;
if (CopyParam->PreserveRights)
{
Properties.Valid = Properties.Valid << vpRights;
Properties.Rights = CopyParam->RemoteFileRights(Attrs);
}
Properties.Valid = Properties.Valid << vpEncrypt;
Properties.Encrypt = CopyParam->EncryptNewFiles;
CreateDirectory(DirectoryPath, &Properties);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DirectorySource(
const UnicodeString & DirectoryName, const UnicodeString & TargetDir, const UnicodeString & DestDirectoryName,
int Attrs, const TCopyParamType * CopyParam, int Params,
TFileOperationProgressType * OperationProgress, unsigned int Flags)
{
FFileSystem->TransferOnDirectory(TargetDir, CopyParam, Params);
UnicodeString DestFullName = UnixIncludeTrailingBackslash(TargetDir + DestDirectoryName);
OperationProgress->SetFile(DirectoryName);
bool PostCreateDir = FLAGCLEAR(Flags, tfPreCreateDir);
// WebDAV and SFTP
if (!PostCreateDir)
{
// This is originally a code for WebDAV, SFTP used a slightly different logic,
// but functionally it should be very similar.
if (!FileExists(DestFullName))
{
CreateTargetDirectory(DestFullName, Attrs, CopyParam);
Flags |= tfNewDirectory;
}
}
if (FLAGCLEAR(Params, cpNoRecurse))
{
int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
TSearchRecChecked SearchRec;
bool FindOK;
FILE_OPERATION_LOOP_BEGIN
{
FindOK =
(FindFirstChecked(DirectoryName + L"*.*", FindAttrs, SearchRec) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(LIST_DIR_ERROR, (DirectoryName)));
try
{
while (FindOK && !OperationProgress->Cancel)
{
UnicodeString FileName = DirectoryName + SearchRec.Name;
try
{
if ((SearchRec.Name != L".") && (SearchRec.Name != L".."))
{
SourceRobust(FileName, DestFullName, CopyParam, Params, OperationProgress, (Flags & ~(tfFirstLevel | tfAutoResume)));
// FTP: if any file got uploaded (i.e. there were any file in the directory and at least one was not skipped),
// do not try to create the directory, as it should be already created by FZAPI during upload
PostCreateDir = false;
}
}
catch (ESkipFile &E)
{
// If ESkipFile occurs, just log it and continue with next file
TSuspendFileOperationProgress Suspend(OperationProgress);
// here a message to user was displayed, which was not appropriate
// when user refused to overwrite the file in subdirectory.
// hopefully it won't be missing in other situations.
if (!HandleException(&E))
{
throw;
}
}
FILE_OPERATION_LOOP_BEGIN
{
FindOK = (FindNextChecked(SearchRec) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(LIST_DIR_ERROR, (DirectoryName)));
}
}
__finally
{
FindClose(SearchRec);
}
// FTP
if (PostCreateDir)
{
TRemoteFile * File = NULL;
// ignore non-fatal error when the directory already exists
bool DoCreate =
!FileExists(DestFullName, &File) ||
!File->IsDirectory; // just try to create and make it fail
delete File;
if (DoCreate)
{
CreateTargetDirectory(DestFullName, Attrs, CopyParam);
}
}
// TODO : Delete also read-only directories.
// TODO : Show error message on failure.
if (!OperationProgress->Cancel)
{
if (IsCapable[fcPreservingTimestampDirs] && CopyParam->PreserveTime && CopyParam->PreserveTimeDirs)
{
TRemoteProperties Properties;
Properties.Valid << vpModification;
OpenLocalFile(
ExcludeTrailingBackslash(DirectoryName), GENERIC_READ, NULL, NULL, NULL,
&Properties.Modification, &Properties.LastAccess, NULL);
ChangeFileProperties(DestFullName, NULL, &Properties);
}
if (FLAGSET(Params, cpDelete))
{
DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
RemoveDir(ApiPath(DirectoryName));
}
else if (CopyParam->ClearArchive && FLAGSET(Attrs, faArchive))
{
FILE_OPERATION_LOOP_BEGIN
{
THROWOSIFFALSE(FileSetAttr(ApiPath(DirectoryName), Attrs & ~faArchive) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DirectoryName)));
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SelectTransferMode(
const UnicodeString & BaseFileName, TOperationSide Side, const TCopyParamType * CopyParam,
const TFileMasks::TParams & MaskParams)
{
bool AsciiTransfer = IsCapable[fcTextMode] && CopyParam->UseAsciiTransfer(BaseFileName, Side, MaskParams);
OperationProgress->SetAsciiTransfer(AsciiTransfer);
if (Configuration->ActualLogProtocol >= 0)
{
UnicodeString ModeName = (OperationProgress->AsciiTransfer ? L"Ascii" : L"Binary");
LogEvent(FORMAT(L"%s transfer mode selected.", (ModeName)));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SelectSourceTransferMode(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam)
{
// Will we use ASCII or BINARY file transfer?
TFileMasks::TParams MaskParams;
MaskParams.Size = Handle.Size;
MaskParams.Modification = Handle.Modification;
UnicodeString BaseFileName = GetBaseFileName(Handle.FileName);
SelectTransferMode(BaseFileName, osLocal, CopyParam, MaskParams);
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::UpdateSource(const TLocalFileHandle & Handle, const TCopyParamType * CopyParam, int Params)
{
// TODO: Delete also read-only files.
if (FLAGSET(Params, cpDelete))
{
if (!Handle.Directory)
{
FILE_OPERATION_LOOP_BEGIN
{
THROWOSIFFALSE(Sysutils::DeleteFile(ApiPath(Handle.FileName)));
}
FILE_OPERATION_LOOP_END(FMTLOAD(DELETE_LOCAL_FILE_ERROR, (Handle.FileName)));
}
}
else if (CopyParam->ClearArchive && FLAGSET(Handle.Attrs, faArchive))
{
FILE_OPERATION_LOOP_BEGIN
{
THROWOSIFFALSE(FileSetAttr(ApiPath(Handle.FileName), (Handle.Attrs & ~faArchive)) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (Handle.FileName)));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::Source(
const UnicodeString & FileName, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
TFileOperationProgressType * OperationProgress, unsigned int Flags, TUploadSessionAction & Action, bool & ChildError)
{
Action.FileName(ExpandUNCFileName(FileName));
OperationProgress->SetFile(FileName, false);
if (!AllowLocalFileTransfer(FileName, CopyParam, OperationProgress))
{
throw ESkipFile();
}
TLocalFileHandle Handle;
OpenLocalFile(FileName, GENERIC_READ, Handle);
OperationProgress->SetFileInProgress();
UnicodeString DestFileName =
ChangeFileName(CopyParam, ExtractFileName(FileName), osLocal, FLAGSET(Flags, tfFirstLevel));
if (Handle.Directory)
{
Action.Cancel();
DirectorySource(
IncludeTrailingBackslash(FileName), TargetDir, DestFileName, Handle.Attrs,
CopyParam, Params, OperationProgress, Flags);
}
else
{
LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", (FileName)));
OperationProgress->SetLocalSize(Handle.Size);
// Suppose same data size to transfer as to read
// (not true with ASCII transfer)
OperationProgress->SetTransferSize(OperationProgress->LocalSize);
if (IsCapable[fcTextMode])
{
SelectSourceTransferMode(Handle, CopyParam);
}
FFileSystem->Source(
Handle, TargetDir, DestFileName, CopyParam, Params, OperationProgress, Flags, Action, ChildError);
LogFileDone(OperationProgress, AbsolutePath(TargetDir + DestFileName, true));
}
Handle.Release();
UpdateSource(Handle, CopyParam, Params);
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CopyToLocal(TStrings * FilesToCopy,
const UnicodeString TargetDir, const TCopyParamType * CopyParam, int Params,
TParallelOperation * ParallelOperation)
{
DebugAssert(FFileSystem);
// see scp.c: sink(), tolocal()
bool Result = false;
DebugAssert(FilesToCopy != NULL);
TOnceDoneOperation OnceDoneOperation = odoIdle;
FDestFileName = L"";
FMultipleDestinationFiles = false;
BeginTransaction();
try
{
__int64 TotalSize;
bool TotalSizeKnown = false;
FLastProgressLogged = GetTickCount();
TFileOperationProgressType OperationProgress(&DoProgress, &DoFinished);
std::unique_ptr<TStringList> Files;
if (CanParallel(CopyParam, Params, ParallelOperation))
{
Files.reset(new TStringList());
Files->OwnsObjects = true;
}
ExceptionOnFail = true;
try
{
// dirty trick: when moving, do not pass copy param to avoid exclude mask
TCalculateSizeStats Stats;
Stats.FoundFiles = Files.get();
if (CalculateFilesSize(
FilesToCopy, TotalSize, csIgnoreErrors,
(FLAGCLEAR(Params, cpDelete) ? CopyParam : NULL),
CopyParam->CalculateSize, Stats))
{
TotalSizeKnown = true;
}
}
__finally
{
ExceptionOnFail = false;
}
OperationProgress.Start((Params & cpDelete ? foMove : foCopy), osRemote,
FilesToCopy->Count, Params & cpTemporary, TargetDir, CopyParam->CPSLimit);
FOperationProgress = &OperationProgress;
bool CollectingUsage = false;
try
{
if (TotalSizeKnown)
{
if (Configuration->Usage->Collect)
{
int CounterTotalSize = TUsage::CalculateCounterSize(TotalSize);
Configuration->Usage->Inc(L"Downloads");
Configuration->Usage->Inc(L"DownloadedBytes", CounterTotalSize);
Configuration->Usage->SetMax(L"MaxDownloadSize", CounterTotalSize);
CollectingUsage = true;
}
OperationProgress.SetTotalSize(TotalSize);
}
try
{
try
{
bool Parallel = TotalSizeKnown && (Files.get() != NULL);
LogTotalTransferDetails(TargetDir, CopyParam, &OperationProgress, Parallel, Files.get());
if (Parallel)
{
// OnceDoneOperation is not supported
ParallelOperation->Init(Files.release(), TargetDir, CopyParam, Params, &OperationProgress, Log->Name);
CopyParallel(ParallelOperation, &OperationProgress);
}
else
{
FFileSystem->CopyToLocal(FilesToCopy, TargetDir, CopyParam, Params,
&OperationProgress, OnceDoneOperation);
}
LogTotalTransferDone(&OperationProgress);
}
__finally
{
if (Active)
{
ReactOnCommand(fsCopyToLocal);
}
}
}
catch (Exception &E)
{
CommandError(&E, MainInstructions(LoadStr(TOLOCAL_COPY_ERROR)));
OnceDoneOperation = odoIdle;
}
if (OperationProgress.Cancel == csContinue)
{
Result = true;
}
}
__finally
{
if (CollectingUsage)
{
int CounterTime = TimeToSeconds(OperationProgress.TimeElapsed());
Configuration->Usage->Inc(L"DownloadTime", CounterTime);
Configuration->Usage->SetMax(L"MaxDownloadTime", CounterTime);
}
FOperationProgress = NULL;
OperationProgress.Stop();
}
}
__finally
{
// If session is still active (no fatal error) we reload directory
// by calling EndTransaction
EndTransaction();
}
if (OnceDoneOperation != odoIdle)
{
UnicodeString DestFileName;
if (!FMultipleDestinationFiles)
{
DestFileName = FDestFileName;
}
CloseOnCompletion(OnceDoneOperation, UnicodeString(), TargetDir, DestFileName);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::DoCopyToLocal(
TStrings * FilesToCopy, const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
TFileOperationProgressType * OperationProgress, unsigned int Flags, TOnceDoneOperation & OnceDoneOperation)
{
DebugAssert((FilesToCopy != NULL) && (OperationProgress != NULL));
UnicodeString FullTargetDir = IncludeTrailingBackslash(TargetDir);
int Index = 0;
while ((Index < FilesToCopy->Count) && !OperationProgress->Cancel)
{
bool Success = false;
UnicodeString FileName = FilesToCopy->Strings[Index];
try
{
try
{
UnicodeString AbsoluteFileName = AbsolutePath(FileName, true);
const TRemoteFile * File = dynamic_cast<const TRemoteFile *>(FilesToCopy->Objects[Index]);
SinkRobust(AbsoluteFileName, File, FullTargetDir, CopyParam, Params, OperationProgress, Flags | tfFirstLevel);
Success = true;
}
catch (ESkipFile & E)
{
TSuspendFileOperationProgress Suspend(OperationProgress);
if (!HandleException(&E))
{
throw;
}
}
}
__finally
{
OperationProgress->Finish(FileName, Success, OnceDoneOperation);
}
Index++;
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SinkRobust(
const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & TargetDir,
const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, unsigned int Flags)
{
TDownloadSessionAction Action(ActionLog);
bool * AFileTransferAny = FLAGSET(Flags, tfUseFileTransferAny) ? &FFileTransferAny : NULL;
TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny);
bool Sunk = false;
do
{
try
{
// If connection is lost while deleting the file, so not retry download on the next round.
// The file may not exist anymore and the download attempt might overwrite the (only) local copy.
if (!Sunk)
{
Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress, Flags, Action);
Sunk = true;
}
if (FLAGSET(Params, cpDelete))
{
DebugAssert(FLAGCLEAR(Params, cpNoRecurse));
// If file is directory, do not delete it recursively, because it should be
// empty already. If not, it should not be deleted (some files were
// skipped or some new files were copied to it, while we were downloading)
int Params = dfNoRecursive;
DeleteFile(FileName, File, &Params);
}
}
catch (Exception & E)
{
if (!RobustLoop.TryReopen(E))
{
if (!Sunk)
{
RollbackAction(Action, OperationProgress, &E);
}
throw;
}
}
if (RobustLoop.ShouldRetry())
{
OperationProgress->RollbackTransfer();
Action.Restart();
DebugAssert(File != NULL);
if (!File->IsDirectory)
{
// prevent overwrite and resume confirmations
Params |= cpNoConfirmation;
Flags |= tfAutoResume;
}
}
}
while (RobustLoop.Retry());
}
//---------------------------------------------------------------------------
struct TSinkFileParams
{
UnicodeString TargetDir;
const TCopyParamType * CopyParam;
int Params;
TFileOperationProgressType * OperationProgress;
bool Skipped;
unsigned int Flags;
};
//---------------------------------------------------------------------------
void __fastcall TTerminal::Sink(
const UnicodeString & FileName, const TRemoteFile * File, const UnicodeString & TargetDir,
const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress, unsigned int Flags,
TDownloadSessionAction & Action)
{
Action.FileName(FileName);
TFileMasks::TParams MaskParams;
DebugAssert(File);
MaskParams.Size = File->Size;
MaskParams.Modification = File->Modification;
UnicodeString BaseFileName = GetBaseFileName(FileName);
if (!CopyParam->AllowTransfer(BaseFileName, osRemote, File->IsDirectory, MaskParams))
{
LogEvent(FORMAT(L"File \"%s\" excluded from transfer", (FileName)));
throw ESkipFile();
}
if (CopyParam->SkipTransfer(FileName, File->IsDirectory))
{
OperationProgress->AddSkippedFileSize(File->Size);
throw ESkipFile();
}
LogFileDetails(FileName, File->Modification, File->Size);
OperationProgress->SetFile(FileName);
UnicodeString OnlyFileName = UnixExtractFileName(FileName);
UnicodeString DestFileName = ChangeFileName(CopyParam, OnlyFileName, osRemote, FLAGSET(Flags, tfFirstLevel));
UnicodeString DestFullName = TargetDir + DestFileName;
if (File->IsDirectory)
{
Action.Cancel();
if (CanRecurseToDirectory(File))
{
FILE_OPERATION_LOOP_BEGIN
{
int Attrs = FileGetAttrFix(ApiPath(DestFullName));
if (FLAGCLEAR(Attrs, faDirectory))
{
EXCEPTION;
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(NOT_DIRECTORY_ERROR, (DestFullName)));
FILE_OPERATION_LOOP_BEGIN
{
THROWOSIFFALSE(ForceDirectories(ApiPath(DestFullName)));
}
FILE_OPERATION_LOOP_END(FMTLOAD(CREATE_DIR_ERROR, (DestFullName)));
if (FLAGCLEAR(Params, cpNoRecurse))
{
TSinkFileParams SinkFileParams;
SinkFileParams.TargetDir = IncludeTrailingBackslash(DestFullName);
SinkFileParams.CopyParam = CopyParam;
SinkFileParams.Params = Params;
SinkFileParams.OperationProgress = OperationProgress;
SinkFileParams.Skipped = false;
SinkFileParams.Flags = Flags & ~(tfFirstLevel | tfAutoResume);
ProcessDirectory(FileName, SinkFile, &SinkFileParams);
FFileSystem->DirectorySunk(DestFullName, File, CopyParam);
// Do not delete directory if some of its files were skip.
// Throw "skip file" for the directory to avoid attempt to deletion
// of any parent directory
if (FLAGSET(Params, cpDelete) && SinkFileParams.Skipped)
{
throw ESkipFile();
}
}
}
else
{
LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (FileName)));
}
}
else
{
LogEvent(FORMAT(L"Copying \"%s\" to local directory started.", (FileName)));
// Will we use ASCII of BINARY file transfer?
SelectTransferMode(BaseFileName, osRemote, CopyParam, MaskParams);
// Suppose same data size to transfer as to write
// (not true with ASCII transfer)
__int64 TransferSize = File->Size;
OperationProgress->SetLocalSize(TransferSize);
if (IsFileEncrypted(FileName))
{
TransferSize += TEncryption::GetOverhead();
}
OperationProgress->SetTransferSize(TransferSize);
int Attrs;
FILE_OPERATION_LOOP_BEGIN
{
Attrs = FileGetAttrFix(ApiPath(DestFullName));
if ((Attrs >= 0) && FLAGSET(Attrs, faDirectory))
{
EXCEPTION;
}
}
FILE_OPERATION_LOOP_END(FMTLOAD(NOT_FILE_ERROR, (DestFullName)));
FFileSystem->Sink(
FileName, File, TargetDir, DestFileName, Attrs, CopyParam, Params, OperationProgress, Flags, Action);
LogFileDone(OperationProgress, ExpandUNCFileName(DestFullName));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::UpdateTargetAttrs(
const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam, int Attrs)
{
if (Attrs == -1)
{
Attrs = faArchive;
}
int NewAttrs = CopyParam->LocalFileAttrs(*File->Rights);
if ((NewAttrs & Attrs) != NewAttrs)
{
FILE_OPERATION_LOOP_BEGIN
{
THROWOSIFFALSE(FileSetAttr(ApiPath(DestFullName), Attrs | NewAttrs) == 0);
}
FILE_OPERATION_LOOP_END(FMTLOAD(CANT_SET_ATTRS, (DestFullName)));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::UpdateTargetTime(HANDLE Handle, TDateTime Modification, TDSTMode DSTMode)
{
LogEvent(FORMAT(L"Preserving timestamp [%s]", (StandardTimestamp(Modification))));
FILETIME WrTime = DateTimeToFileTime(Modification, DSTMode);
if (!SetFileTime(Handle, NULL, NULL, &WrTime))
{
int Error = GetLastError();
LogEvent(FORMAT(L"Preserving timestamp failed, ignoring: %s", (SysErrorMessageForError(Error))));
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::SinkFile(UnicodeString FileName, const TRemoteFile * File, void * Param)
{
TSinkFileParams * Params = static_cast<TSinkFileParams *>(Param);
DebugAssert(Params->OperationProgress != NULL);
try
{
SinkRobust(FileName, File, Params->TargetDir, Params->CopyParam,
Params->Params, Params->OperationProgress, Params->Flags);
}
catch (ESkipFile & E)
{
Params->Skipped = true;
{
TSuspendFileOperationProgress Suspend(Params->OperationProgress);
if (!HandleException(&E))
{
throw;
}
}
if (Params->OperationProgress->Cancel)
{
Abort();
}
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::ReflectSettings()
{
DebugAssert(FLog != NULL);
FLog->ReflectSettings();
DebugAssert(FActionLog != NULL);
FActionLog->ReflectSettings();
// also FTunnelLog ?
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CollectUsage()
{
switch (SessionData->FSProtocol)
{
case fsSCPonly:
Configuration->Usage->Inc(L"OpenedSessionsSCP");
break;
case fsSFTP:
case fsSFTPonly:
Configuration->Usage->Inc(L"OpenedSessionsSFTP");
break;
case fsFTP:
if (SessionData->Ftps == ftpsNone)
{
Configuration->Usage->Inc(L"OpenedSessionsFTP");
}
else
{
Configuration->Usage->Inc(L"OpenedSessionsFTPS");
}
break;
case fsWebDAV:
if (SessionData->Ftps == ftpsNone)
{
Configuration->Usage->Inc(L"OpenedSessionsWebDAV");
}
else
{
Configuration->Usage->Inc(L"OpenedSessionsWebDAVS");
}
break;
case fsS3:
Configuration->Usage->Inc(L"OpenedSessionsS3");
break;
}
if (Configuration->Logging && Configuration->LogToFile)
{
Configuration->Usage->Inc(L"OpenedSessionsLogToFile2");
}
if (Configuration->LogActions)
{
Configuration->Usage->Inc(L"OpenedSessionsXmlLog");
}
std::unique_ptr<TSessionData> FactoryDefaults(new TSessionData(L""));
if (!SessionData->IsSame(FactoryDefaults.get(), true))
{
Configuration->Usage->Inc(L"OpenedSessionsAdvanced");
}
if (SessionData->ProxyMethod != ::pmNone)
{
Configuration->Usage->Inc(L"OpenedSessionsProxy");
}
if (SessionData->FtpProxyLogonType > 0)
{
Configuration->Usage->Inc(L"OpenedSessionsFtpProxy");
}
if (IsEncryptingFiles())
{
Configuration->Usage->Inc(L"OpenedSessionsEncrypted");
}
FCollectFileSystemUsage = true;
}
//---------------------------------------------------------------------------
static UnicodeString __fastcall FormatCertificateData(const UnicodeString & Fingerprint, int Failures)
{
return FORMAT(L"%s;%2.2X", (Fingerprint, Failures));
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::VerifyCertificate(
const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey,
const UnicodeString & Fingerprint,
const UnicodeString & CertificateSubject, int Failures)
{
bool Result = false;
UnicodeString CertificateData = FormatCertificateData(Fingerprint, Failures);
std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateConfigStorage());
Storage->AccessMode = smRead;
if (Storage->OpenSubKey(CertificateStorageKey, false))
{
if (Storage->ValueExists(SiteKey))
{
UnicodeString CachedCertificateData = Storage->ReadString(SiteKey, L"");
if (CertificateData == CachedCertificateData)
{
LogEvent(FORMAT(L"Certificate for \"%s\" matches cached fingerprint and failures", (CertificateSubject)));
Result = true;
}
}
else if (Storage->ValueExists(Fingerprint))
{
LogEvent(FORMAT(L"Certificate for \"%s\" matches legacy cached fingerprint", (CertificateSubject)));
Result = true;
}
}
if (!Result)
{
UnicodeString Buf = SessionData->HostKey;
while (!Result && !Buf.IsEmpty())
{
UnicodeString ExpectedKey = CutToChar(Buf, L';', false);
if (ExpectedKey == L"*")
{
UnicodeString Message = LoadStr(ANY_CERTIFICATE);
Information(Message, true);
Log->Add(llException, Message);
Result = true;
}
else if (ExpectedKey == Fingerprint)
{
LogEvent(FORMAT(L"Certificate for \"%s\" matches configured fingerprint", (CertificateSubject)));
Result = true;
}
}
}
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::ConfirmCertificate(
TSessionInfo & SessionInfo, int Failures, const UnicodeString & CertificateStorageKey, bool CanRemember)
{
TClipboardHandler ClipboardHandler;
ClipboardHandler.Text = SessionInfo.CertificateFingerprint;
TQueryButtonAlias Aliases[1];
Aliases[0].Button = qaRetry;
Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON);
Aliases[0].ActionAlias = LoadStr(COPY_CERTIFICATE_ACTION);
Aliases[0].OnSubmit = &ClipboardHandler.Copy;
TQueryParams Params(qpWaitInBatch);
Params.HelpKeyword = HELP_VERIFY_CERTIFICATE;
Params.NoBatchAnswers = qaYes | qaRetry;
Params.Aliases = Aliases;
Params.AliasesCount = LENOF(Aliases);
unsigned int Answer =
QueryUser(
FMTLOAD(VERIFY_CERT_PROMPT3, (SessionInfo.Certificate)),
NULL, qaYes | qaNo | qaCancel | qaRetry, &Params, qtWarning);
bool Result;
switch (Answer)
{
case qaYes:
CacheCertificate(
CertificateStorageKey, SessionData->SiteKey, SessionInfo.CertificateFingerprint, Failures);
Result = true;
break;
case qaNo:
Result = true;
break;
case qaCancel:
Configuration->Usage->Inc(L"HostNotVerified");
Result = false;
break;
default:
DebugFail();
Result = false;
break;
}
// Cache only if the certificate was accepted manually
if (Result && CanRemember)
{
Configuration->RememberLastFingerprint(
SessionData->SiteKey, TlsFingerprintType, SessionInfo.CertificateFingerprint);
}
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CacheCertificate(const UnicodeString & CertificateStorageKey,
const UnicodeString & SiteKey, const UnicodeString & Fingerprint, int Failures)
{
UnicodeString CertificateData = FormatCertificateData(Fingerprint, Failures);
std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateConfigStorage());
Storage->AccessMode = smReadWrite;
if (Storage->OpenSubKey(CertificateStorageKey, true))
{
Storage->WriteString(SiteKey, CertificateData);
}
}
//---------------------------------------------------------------------------
void __fastcall TTerminal::CollectTlsUsage(const UnicodeString & TlsVersionStr)
{
// see SSL_get_version() in OpenSSL ssl_lib.c
if (TlsVersionStr == L"TLSv1.2")
{
Configuration->Usage->Inc(L"OpenedSessionsTLS12");
}
else if (TlsVersionStr == L"TLSv1.1")
{
Configuration->Usage->Inc(L"OpenedSessionsTLS11");
}
else if (TlsVersionStr == L"TLSv1")
{
Configuration->Usage->Inc(L"OpenedSessionsTLS10");
}
else if (TlsVersionStr == L"SSLv3")
{
Configuration->Usage->Inc(L"OpenedSessionsSSL30");
}
else if (TlsVersionStr == L"SSLv2")
{
Configuration->Usage->Inc(L"OpenedSessionsSSL20");
}
else
{
DebugFail();
}
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::LoadTlsCertificate(X509 *& Certificate, EVP_PKEY *& PrivateKey)
{
bool Result = !SessionData->TlsCertificateFile.IsEmpty();
if (Result)
{
UnicodeString Passphrase = SessionData->Passphrase;
// Inspired by neon's ne_ssl_clicert_read
bool Retry;
do
{
Retry = false;
bool WrongPassphrase = false;
ParseCertificate(SessionData->TlsCertificateFile, Passphrase, Certificate, PrivateKey, WrongPassphrase);
if (WrongPassphrase)
{
if (Passphrase.IsEmpty())
{
LogEvent(L"Certificate is encrypted, need passphrase");
Information(LoadStr(CLIENT_CERTIFICATE_LOADING), false);
}
else
{
Information(LoadStr(CERTIFICATE_DECODE_ERROR_INFO), false);
}
Passphrase = L"";
if (PromptUser(
SessionData, pkPassphrase,
LoadStr(CERTIFICATE_PASSPHRASE_TITLE), L"",
LoadStr(CERTIFICATE_PASSPHRASE_PROMPT), false, 0, Passphrase))
{
Retry = true;
}
else
{
Result = false;
}
}
}
while (Retry);
}
return Result;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::GetBaseFileName(UnicodeString FileName)
{
if (FSessionData->TrimVMSVersions)
{
int P = FileName.LastDelimiter(L";");
if (P > 0)
{
FileName.SetLength(P - 1);
}
}
return FileName;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::ChangeFileName(const TCopyParamType * CopyParam,
UnicodeString FileName, TOperationSide Side, bool FirstLevel)
{
FileName = GetBaseFileName(FileName);
FileName = CopyParam->ChangeFileName(FileName, Side, FirstLevel);
return FileName;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::CanRecurseToDirectory(const TRemoteFile * File)
{
return !File->IsSymLink || FSessionData->FollowDirectorySymlinks;
}
//---------------------------------------------------------------------------
TTerminal::TEncryptedFileNames::const_iterator __fastcall TTerminal::GetEncryptedFileName(const UnicodeString & Path)
{
UnicodeString FileDir = UnixExtractFileDir(Path);
// If we haven't been in this folder yet, read it to collect mapping to encrypted file names.
if (FFoldersScannedForEncryptedFiles.find(FileDir) == FFoldersScannedForEncryptedFiles.end())
{
try
{
delete DoReadDirectoryListing(FileDir, true);
}
catch (Exception & E)
{
if (!Active)
{
throw;
}
}
FFoldersScannedForEncryptedFiles.insert(FileDir);
}
TEncryptedFileNames::const_iterator Result = FEncryptedFileNames.find(Path);
return Result;
}
//---------------------------------------------------------------------------
bool __fastcall TTerminal::IsFileEncrypted(const UnicodeString & Path, bool EncryptNewFiles)
{
// can be optimized
bool Result = (EncryptFileName(Path, EncryptNewFiles) != Path);
return Result;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::EncryptFileName(const UnicodeString & Path, bool EncryptNewFiles)
{
UnicodeString Result = Path;
if (IsEncryptingFiles() && !IsUnixRootPath(Path))
{
UnicodeString FileName = UnixExtractFileName(Path);
UnicodeString FileDir = UnixExtractFileDir(Path);
if (!FileName.IsEmpty() && (FileName != PARENTDIRECTORY) && (FileName != THISDIRECTORY))
{
TEncryptedFileNames::const_iterator I = GetEncryptedFileName(Path);
if (I != FEncryptedFileNames.end())
{
FileName = I->second;
}
else if (EncryptNewFiles)
{
TEncryption Encryption(FEncryptKey);
FileName = Encryption.EncryptFileName(FileName);
FEncryptedFileNames.insert(std::make_pair(Path, FileName));
}
}
FileDir = EncryptFileName(FileDir, EncryptNewFiles);
Result = UnixCombinePaths(FileDir, FileName);
}
return Result;
}
//---------------------------------------------------------------------------
UnicodeString __fastcall TTerminal::DecryptFileName(const UnicodeString & Path)
{
UnicodeString Result = Path;
if (IsEncryptingFiles() && !IsUnixRootPath(Path))
{
UnicodeString FileName = UnixExtractFileName(Path);
UnicodeString FileNameEncrypted = FileName;
bool Encrypted = TEncryption::IsEncryptedFileName(FileName);
if (Encrypted)
{
TEncryption Encryption(FEncryptKey);
FileName = Encryption.DecryptFileName(FileName);
UnicodeString FileDir = UnixExtractFileDir(Path);
FileDir = DecryptFileName(FileDir);
Result = UnixCombinePaths(FileDir, FileName);
}
if (Encrypted || (FEncryptedFileNames.find(Result) == FEncryptedFileNames.end()))
{
// This may overwrite another variant of encryption
FEncryptedFileNames[Result] = FileNameEncrypted;
}
}
return Result;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
__fastcall TSecondaryTerminal::TSecondaryTerminal(TTerminal * MainTerminal,
TSessionData * ASessionData, TConfiguration * Configuration, const UnicodeString & Name) :
TTerminal(ASessionData, Configuration),
FMainTerminal(MainTerminal)
{
Log->SetParent(FMainTerminal->Log, Name);
ActionLog->Enabled = false;
SessionData->NonPersistant();
DebugAssert(FMainTerminal != NULL);
if (!FMainTerminal->UserName.IsEmpty())
{
SessionData->UserName = FMainTerminal->UserName;
}
}
//---------------------------------------------------------------------------
void __fastcall TSecondaryTerminal::UpdateFromMain()
{
if ((FFileSystem != NULL) && (FMainTerminal->FFileSystem != NULL))
{
FFileSystem->UpdateFromMain(FMainTerminal->FFileSystem);
}
}
//---------------------------------------------------------------------------
void __fastcall TSecondaryTerminal::DirectoryLoaded(TRemoteFileList * FileList)
{
FMainTerminal->DirectoryLoaded(FileList);
DebugAssert(FileList != NULL);
}
//---------------------------------------------------------------------------
void __fastcall TSecondaryTerminal::DirectoryModified(const UnicodeString Path,
bool SubDirs)
{
// clear cache of main terminal
FMainTerminal->DirectoryModified(Path, SubDirs);
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TSecondaryTerminal::GetPasswordSource()
{
return FMainTerminal;
}
//---------------------------------------------------------------------------
__fastcall TTerminalList::TTerminalList(TConfiguration * AConfiguration) :
TObjectList()
{
DebugAssert(AConfiguration);
FConfiguration = AConfiguration;
}
//---------------------------------------------------------------------------
__fastcall TTerminalList::~TTerminalList()
{
DebugAssert(Count == 0);
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TTerminalList::CreateTerminal(TSessionData * Data)
{
return new TTerminal(Data, FConfiguration);
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TTerminalList::NewTerminal(TSessionData * Data)
{
TTerminal * Terminal = CreateTerminal(Data);
Add(Terminal);
return Terminal;
}
//---------------------------------------------------------------------------
void __fastcall TTerminalList::FreeTerminal(TTerminal * Terminal)
{
DebugAssert(IndexOf(Terminal) >= 0);
Remove(Terminal);
}
//---------------------------------------------------------------------------
void __fastcall TTerminalList::FreeAndNullTerminal(TTerminal * & Terminal)
{
TTerminal * T = Terminal;
Terminal = NULL;
FreeTerminal(T);
}
//---------------------------------------------------------------------------
TTerminal * __fastcall TTerminalList::GetTerminal(int Index)
{
return dynamic_cast<TTerminal *>(Items[Index]);
}
//---------------------------------------------------------------------------
void __fastcall TTerminalList::RecryptPasswords()
{
for (int Index = 0; Index < Count; Index++)
{
Terminals[Index]->RecryptPasswords();
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TLocalFileHandle::TLocalFileHandle()
{
Handle = 0;
Attrs = 0;
MTime = 0;
ATime = 0;
Size = 0;
Directory = false;
}
//---------------------------------------------------------------------------
TLocalFileHandle::~TLocalFileHandle()
{
Release();
}
//---------------------------------------------------------------------------
void TLocalFileHandle::Release()
{
if (Handle != 0)
{
Close();
}
}
//---------------------------------------------------------------------------
void TLocalFileHandle::Dismiss()
{
if (DebugAlwaysTrue(Handle != 0))
{
Handle = 0;
}
}
//---------------------------------------------------------------------------
void TLocalFileHandle::Close()
{
if (DebugAlwaysTrue(Handle != 0))
{
CloseHandle(Handle);
Dismiss();
}
}