|
@@ -187,6 +187,12 @@ struct TFileTransferData
|
|
const int tfFirstLevel = 0x01;
|
|
const int tfFirstLevel = 0x01;
|
|
const int tfAutoResume = 0x02;
|
|
const int tfAutoResume = 0x02;
|
|
const wchar_t CertificateStorageKey[] = L"FtpsCertificates";
|
|
const wchar_t CertificateStorageKey[] = L"FtpsCertificates";
|
|
|
|
+const UnicodeString SiteCommand(L"SITE");
|
|
|
|
+const UnicodeString SymlinkSiteCommand(L"SYMLINK");
|
|
|
|
+const UnicodeString CopySiteCommand(L"COPY");
|
|
|
|
+const UnicodeString HashCommand(L"HASH");
|
|
|
|
+const UnicodeString AvblCommand(L"AVBL");
|
|
|
|
+const UnicodeString XQuotaCommand(L"XQUOTA");
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
struct TSinkFileParams
|
|
struct TSinkFileParams
|
|
{
|
|
{
|
|
@@ -256,6 +262,17 @@ __fastcall TFTPFileSystem::TFTPFileSystem(TTerminal * ATerminal):
|
|
FTimeoutStatus = LoadStr(IDS_ERRORMSG_TIMEOUT);
|
|
FTimeoutStatus = LoadStr(IDS_ERRORMSG_TIMEOUT);
|
|
FDisconnectStatus = LoadStr(IDS_STATUSMSG_DISCONNECTED);
|
|
FDisconnectStatus = LoadStr(IDS_STATUSMSG_DISCONNECTED);
|
|
FServerCapabilities = new TFTPServerCapabilities();
|
|
FServerCapabilities = new TFTPServerCapabilities();
|
|
|
|
+ FHashAlgs.reset(new TStringList());
|
|
|
|
+ FSupportedCommands.reset(CreateSortedStringList());
|
|
|
|
+ FSupportedSiteCommands.reset(CreateSortedStringList());
|
|
|
|
+
|
|
|
|
+ FChecksumAlgs.reset(new TStringList());
|
|
|
|
+ FChecksumCommands.reset(new TStringList());
|
|
|
|
+ RegisterChecksumAlgCommand(Sha1ChecksumAlg, L"XSHA1");
|
|
|
|
+ RegisterChecksumAlgCommand(Sha256ChecksumAlg, L"XSHA256");
|
|
|
|
+ RegisterChecksumAlgCommand(Sha512ChecksumAlg, L"XSHA512");
|
|
|
|
+ RegisterChecksumAlgCommand(Md5ChecksumAlg, L"XMD5");
|
|
|
|
+ RegisterChecksumAlgCommand(Crc32ChecksumAlg, L"XCRC");
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TFTPFileSystem::~TFTPFileSystem()
|
|
__fastcall TFTPFileSystem::~TFTPFileSystem()
|
|
@@ -391,10 +408,6 @@ void __fastcall TFTPFileSystem::Open()
|
|
|
|
|
|
int Pasv = (Data->FtpPasvMode ? 1 : 2);
|
|
int Pasv = (Data->FtpPasvMode ? 1 : 2);
|
|
|
|
|
|
- FDetectTimeDifference = Data->TimeDifferenceAuto;
|
|
|
|
- FTimeDifference = 0;
|
|
|
|
- FSupportsSiteCopy = false;
|
|
|
|
- FSupportsSiteSymlink = false;
|
|
|
|
int TimeZoneOffset = Data->TimeDifferenceAuto ? 0 : TimeToMinutes(Data->TimeDifference);
|
|
int TimeZoneOffset = Data->TimeDifferenceAuto ? 0 : TimeToMinutes(Data->TimeDifference);
|
|
|
|
|
|
int UTF8 = 0;
|
|
int UTF8 = 0;
|
|
@@ -413,12 +426,16 @@ void __fastcall TFTPFileSystem::Open()
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ FPasswordFailed = false;
|
|
bool PromptedForCredentials = false;
|
|
bool PromptedForCredentials = false;
|
|
|
|
|
|
do
|
|
do
|
|
{
|
|
{
|
|
|
|
+ FDetectTimeDifference = Data->TimeDifferenceAuto;
|
|
|
|
+ FTimeDifference = 0;
|
|
|
|
+ ResetFeatures();
|
|
FSystem = L"";
|
|
FSystem = L"";
|
|
- FFeatures->Clear();
|
|
|
|
|
|
+ FWelcomeMessage = L"";
|
|
FFileSystemInfoValid = false;
|
|
FFileSystemInfoValid = false;
|
|
|
|
|
|
// TODO: the same for account? it ever used?
|
|
// TODO: the same for account? it ever used?
|
|
@@ -446,6 +463,21 @@ void __fastcall TFTPFileSystem::Open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // On retry ask for password.
|
|
|
|
+ // This is particularly important, when stored password is no longer valid,
|
|
|
|
+ // so we do not blindly try keep trying it in a loop (possibly causing account lockout)
|
|
|
|
+ if (FPasswordFailed)
|
|
|
|
+ {
|
|
|
|
+ FTerminal->LogEvent(L"Password prompt (last login attempt failed)");
|
|
|
|
+
|
|
|
|
+ Password = L"";
|
|
|
|
+ if (!FTerminal->PromptUser(Data, pkPassword, LoadStr(PASSWORD_TITLE), L"",
|
|
|
|
+ LoadStr(PASSWORD_PROMPT), false, 0, Password))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->FatalError(NULL, LoadStr(AUTHENTICATION_FAILED));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
FPasswordFailed = false;
|
|
FPasswordFailed = false;
|
|
TAutoFlag OpeningFlag(FOpening);
|
|
TAutoFlag OpeningFlag(FOpening);
|
|
|
|
|
|
@@ -591,21 +623,21 @@ void __fastcall TFTPFileSystem::CollectUsage()
|
|
// SYST
|
|
// SYST
|
|
// 215 UNIX emulated by FileZilla
|
|
// 215 UNIX emulated by FileZilla
|
|
// (Welcome message is configurable)
|
|
// (Welcome message is configurable)
|
|
- if (ContainsText(FSystem, "FileZilla"))
|
|
|
|
|
|
+ if (ContainsText(FSystem, L"FileZilla"))
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPFileZilla");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPFileZilla");
|
|
}
|
|
}
|
|
// 220 ProFTPD 1.3.4a Server (Debian) [::ffff:192.168.179.137]
|
|
// 220 ProFTPD 1.3.4a Server (Debian) [::ffff:192.168.179.137]
|
|
// SYST
|
|
// SYST
|
|
// 215 UNIX Type: L8
|
|
// 215 UNIX Type: L8
|
|
- else if (ContainsText(FWelcomeMessage, "ProFTPD"))
|
|
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"ProFTPD"))
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPProFTPD");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPProFTPD");
|
|
}
|
|
}
|
|
// 220 Microsoft FTP Service
|
|
// 220 Microsoft FTP Service
|
|
// SYST
|
|
// SYST
|
|
// 215 Windows_NT
|
|
// 215 Windows_NT
|
|
- else if (ContainsText(FWelcomeMessage, "Microsoft FTP Service"))
|
|
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"Microsoft FTP Service"))
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPIIS");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPIIS");
|
|
}
|
|
}
|
|
@@ -613,7 +645,7 @@ void __fastcall TFTPFileSystem::CollectUsage()
|
|
// SYST
|
|
// SYST
|
|
// 215 UNIX Type: L8
|
|
// 215 UNIX Type: L8
|
|
// (Welcome message is configurable)
|
|
// (Welcome message is configurable)
|
|
- else if (ContainsText(FWelcomeMessage, "vsFTPd"))
|
|
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"vsFTPd"))
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPvsFTPd");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPvsFTPd");
|
|
}
|
|
}
|
|
@@ -621,7 +653,7 @@ void __fastcall TFTPFileSystem::CollectUsage()
|
|
// ...
|
|
// ...
|
|
// SYST
|
|
// SYST
|
|
// 215 UNIX Type: L8
|
|
// 215 UNIX Type: L8
|
|
- else if (ContainsText(FWelcomeMessage, "Pure-FTPd"))
|
|
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"Pure-FTPd"))
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPPureFTPd");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPPureFTPd");
|
|
}
|
|
}
|
|
@@ -629,10 +661,52 @@ void __fastcall TFTPFileSystem::CollectUsage()
|
|
// ...
|
|
// ...
|
|
// SYST
|
|
// SYST
|
|
// 215 UNIX Type: L8
|
|
// 215 UNIX Type: L8
|
|
- else if (ContainsText(FWelcomeMessage, "Titan FTP Server"))
|
|
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"Titan FTP Server"))
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPTitan");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPTitan");
|
|
}
|
|
}
|
|
|
|
+ // 220-Cerberus FTP Server - Home Edition
|
|
|
|
+ // 220-This is the UNLICENSED Home Edition and may be used for home, personal use only
|
|
|
|
+ // 220-Welcome to Cerberus FTP Server
|
|
|
|
+ // 220 Created by Cerberus, LLC
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"Cerberus FTP Server"))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPCerberus");
|
|
|
|
+ }
|
|
|
|
+ // 220 Serv-U FTP Server v15.1 ready...
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"Serv-U FTP Server"))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPServU");
|
|
|
|
+ }
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"WS_FTP"))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPWSFTP");
|
|
|
|
+ }
|
|
|
|
+ // 220 Welcome to the most popular FTP hosting service! Save on hardware, software, hosting and admin. Share files/folders with read-write permission. Visit http://www.drivehq.com/ftp/;
|
|
|
|
+ // ...
|
|
|
|
+ // SYST
|
|
|
|
+ // 215 UNIX emulated by DriveHQ FTP Server.
|
|
|
|
+ else if (ContainsText(FSystem, L"DriveHQ"))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPDriveHQ");
|
|
|
|
+ }
|
|
|
|
+ // 220 GlobalSCAPE EFT Server (v. 6.0) * UNREGISTERED COPY *
|
|
|
|
+ // ...
|
|
|
|
+ // SYST
|
|
|
|
+ // 215 UNIX Type: L8
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"GlobalSCAPE"))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPGlobalScape");
|
|
|
|
+ }
|
|
|
|
+ // 220-<custom message>
|
|
|
|
+ // 220 CompleteFTP v 8.1.3
|
|
|
|
+ // ...
|
|
|
|
+ // SYST
|
|
|
|
+ // UNIX Type: L8
|
|
|
|
+ else if (ContainsText(FWelcomeMessage, L"CompleteFTP"))
|
|
|
|
+ {
|
|
|
|
+ FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPComplete");
|
|
|
|
+ }
|
|
else
|
|
else
|
|
{
|
|
{
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPOther");
|
|
FTerminal->Configuration->Usage->Inc(L"OpenedSessionsFTPOther");
|
|
@@ -740,7 +814,7 @@ void __fastcall TFTPFileSystem::AnyCommand(const UnicodeString Command,
|
|
FOnCaptureOutput = OutputEvent;
|
|
FOnCaptureOutput = OutputEvent;
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
|
|
+ SendCommand(Command);
|
|
|
|
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_3XX_CODE);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_3XX_CODE);
|
|
}
|
|
}
|
|
@@ -764,7 +838,7 @@ void __fastcall TFTPFileSystem::AnnounceFileListOperation()
|
|
void __fastcall TFTPFileSystem::DoChangeDirectory(const UnicodeString & Directory)
|
|
void __fastcall TFTPFileSystem::DoChangeDirectory(const UnicodeString & Directory)
|
|
{
|
|
{
|
|
UnicodeString Command = FORMAT(L"CWD %s", (Directory));
|
|
UnicodeString Command = FORMAT(L"CWD %s", (Directory));
|
|
- FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
|
|
+ SendCommand(Command);
|
|
|
|
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
}
|
|
}
|
|
@@ -882,11 +956,233 @@ bool __fastcall TFTPFileSystem::LoadFilesProperties(TStrings * /*FileList*/)
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
-void __fastcall TFTPFileSystem::CalculateFilesChecksum(const UnicodeString & /*Alg*/,
|
|
|
|
- TStrings * /*FileList*/, TStrings * /*Checksums*/,
|
|
|
|
- TCalculatedChecksumEvent /*OnCalculatedChecksum*/)
|
|
|
|
|
|
+UnicodeString __fastcall TFTPFileSystem::DoCalculateFileChecksum(
|
|
|
|
+ bool UsingHashCommand, const UnicodeString & Alg, TRemoteFile * File)
|
|
{
|
|
{
|
|
- FAIL;
|
|
|
|
|
|
+ UnicodeString CommandName;
|
|
|
|
+
|
|
|
|
+ if (UsingHashCommand)
|
|
|
|
+ {
|
|
|
|
+ CommandName = HashCommand;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ int Index = FChecksumAlgs->IndexOf(Alg);
|
|
|
|
+ if (Index < 0)
|
|
|
|
+ {
|
|
|
|
+ FAIL;
|
|
|
|
+ EXCEPTION;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ CommandName = FChecksumCommands->Strings[Index];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ UnicodeString FileName = File->FullFileName;
|
|
|
|
+ // FTP way is not to quote.
|
|
|
|
+ // But as Serv-U, GlobalSCAPE and possibly others allow
|
|
|
|
+ // additional parameters (SP ER range), they need to quote file name.
|
|
|
|
+ // Cerberus and FileZilla Server on the other hand can do without quotes
|
|
|
|
+ // (but they can handle them, not sure about other servers)
|
|
|
|
+
|
|
|
|
+ // Quoting:
|
|
|
|
+ // FileZilla Server simply checks if argument starts and ends with double-quote
|
|
|
|
+ // and strips them, no double-quote escaping is possible.
|
|
|
|
+ // That's for all commands, not just HASH
|
|
|
|
+ // ProFTPD: TODO: Check how "SITE SYMLINK target link" is parsed
|
|
|
|
+
|
|
|
|
+ // We can possibly autodetect this from announced command format:
|
|
|
|
+ // XCRC filename;start;end
|
|
|
|
+ // XMD5 filename;start;end
|
|
|
|
+ // XSHA1 filename;start;end
|
|
|
|
+ // XSHA256 filename;start;end
|
|
|
|
+ // XSHA512 filename;start;end
|
|
|
|
+ if (FileName.Pos(L" ") > 0)
|
|
|
|
+ {
|
|
|
|
+ FileName = FORMAT(L"\"%s\"", (FileName));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ UnicodeString Command = FORMAT(L"%s %s", (CommandName, FileName));
|
|
|
|
+ SendCommand(Command);
|
|
|
|
+ UnicodeString ResponseText = GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_SINGLE_LINE);
|
|
|
|
+
|
|
|
|
+ UnicodeString Hash;
|
|
|
|
+ if (UsingHashCommand)
|
|
|
|
+ {
|
|
|
|
+ // Code should be 213, but let's be tolerant and accept any 2xx
|
|
|
|
+
|
|
|
|
+ // ("213" SP) hashname SP start-point "-" end-point SP filehash SP <pathname> (CRLF)
|
|
|
|
+ UnicodeString Buf = ResponseText;
|
|
|
|
+ // skip alg
|
|
|
|
+ CutToChar(Buf, L' ', true);
|
|
|
|
+ // skip range
|
|
|
|
+ UnicodeString Range = CutToChar(Buf, L' ', true);
|
|
|
|
+ // This should be range (SP-EP), but if it does not conform to the format,
|
|
|
|
+ // it's likely because the server uses version of the HASH spec
|
|
|
|
+ // before draft-ietf-ftpext2-hash-01
|
|
|
|
+ // (including draft-bryan-ftp-hash-06 implemented by FileZilla server; or Cerberus),
|
|
|
|
+ // that did not have the "range" part.
|
|
|
|
+ // The FileZilla Server even omits the file name.
|
|
|
|
+ // The latest draft as of implementing this is draft-bryan-ftpext-hash-02.
|
|
|
|
+ if (Range.Pos(L"-") > 0)
|
|
|
|
+ {
|
|
|
|
+ Hash = CutToChar(Buf, L' ', true);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Hash = Range;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else // All X<hash> commands
|
|
|
|
+ {
|
|
|
|
+ // Accepting any 2xx response. Most servers use 213,
|
|
|
|
+ // but for example WS_FTP uses non-sense code 220 (Service ready for new user)
|
|
|
|
+ Hash = ResponseText;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (Hash.IsEmpty())
|
|
|
|
+ {
|
|
|
|
+ throw Exception(FMTLOAD(FTP_RESPONSE_ERROR, (CommandName, ResponseText)));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return LowerCase(Hash);
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+void __fastcall TFTPFileSystem::DoCalculateFilesChecksum(bool UsingHashCommand,
|
|
|
|
+ const UnicodeString & Alg, TStrings * FileList, TStrings * Checksums,
|
|
|
|
+ TCalculatedChecksumEvent OnCalculatedChecksum,
|
|
|
|
+ TFileOperationProgressType * OperationProgress, bool FirstLevel)
|
|
|
|
+{
|
|
|
|
+ TOnceDoneOperation OnceDoneOperation; // not used
|
|
|
|
+
|
|
|
|
+ int Index = 0;
|
|
|
|
+ while ((Index < FileList->Count) && !OperationProgress->Cancel)
|
|
|
|
+ {
|
|
|
|
+ TRemoteFile * File = (TRemoteFile *)FileList->Objects[Index];
|
|
|
|
+ assert(File != NULL);
|
|
|
|
+
|
|
|
|
+ if (File->IsDirectory)
|
|
|
|
+ {
|
|
|
|
+ if (!File->IsSymLink &&
|
|
|
|
+ !File->IsParentDirectory && !File->IsThisDirectory &&
|
|
|
|
+ // recurse into subdirectories only if we have callback function
|
|
|
|
+ (OnCalculatedChecksum != NULL))
|
|
|
|
+ {
|
|
|
|
+ OperationProgress->SetFile(File->FileName);
|
|
|
|
+ TRemoteFileList * SubFiles =
|
|
|
|
+ FTerminal->CustomReadDirectoryListing(File->FullFileName, false);
|
|
|
|
+
|
|
|
|
+ if (SubFiles != NULL)
|
|
|
|
+ {
|
|
|
|
+ TStrings * SubFileList = new TStringList();
|
|
|
|
+ bool Success = false;
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ OperationProgress->SetFile(File->FileName);
|
|
|
|
+
|
|
|
|
+ for (int Index = 0; Index < SubFiles->Count; Index++)
|
|
|
|
+ {
|
|
|
|
+ TRemoteFile * SubFile = SubFiles->Files[Index];
|
|
|
|
+ SubFileList->AddObject(SubFile->FullFileName, SubFile);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // do not collect checksums for files in subdirectories,
|
|
|
|
+ // only send back checksums via callback
|
|
|
|
+ DoCalculateFilesChecksum(UsingHashCommand, Alg, SubFileList, NULL,
|
|
|
|
+ OnCalculatedChecksum, OperationProgress, false);
|
|
|
|
+
|
|
|
|
+ Success = true;
|
|
|
|
+ }
|
|
|
|
+ __finally
|
|
|
|
+ {
|
|
|
|
+ delete SubFiles;
|
|
|
|
+ delete SubFileList;
|
|
|
|
+
|
|
|
|
+ if (FirstLevel)
|
|
|
|
+ {
|
|
|
|
+ OperationProgress->Finish(File->FileName, Success, OnceDoneOperation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ TChecksumSessionAction Action(FTerminal->ActionLog);
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ OperationProgress->SetFile(File->FileName);
|
|
|
|
+ Action.FileName(FTerminal->AbsolutePath(File->FullFileName, true));
|
|
|
|
+
|
|
|
|
+ UnicodeString Checksum = DoCalculateFileChecksum(UsingHashCommand, Alg, File);
|
|
|
|
+
|
|
|
|
+ if (OnCalculatedChecksum != NULL)
|
|
|
|
+ {
|
|
|
|
+ OnCalculatedChecksum(File->FileName, Alg, Checksum);
|
|
|
|
+ }
|
|
|
|
+ Action.Checksum(Alg, Checksum);
|
|
|
|
+ if (Checksums != NULL)
|
|
|
|
+ {
|
|
|
|
+ Checksums->Add(Checksum);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (Exception & E)
|
|
|
|
+ {
|
|
|
|
+ FTerminal->RollbackAction(Action, OperationProgress, &E);
|
|
|
|
+
|
|
|
|
+ // Error formatting expanded from inline to avoid strange exceptions
|
|
|
|
+ UnicodeString Error =
|
|
|
|
+ FMTLOAD(CHECKSUM_ERROR,
|
|
|
|
+ (File != NULL ? File->FullFileName : UnicodeString(L"")));
|
|
|
|
+ FTerminal->CommandError(&E, Error);
|
|
|
|
+ // Abort loop.
|
|
|
|
+ // TODO: retries? resume?
|
|
|
|
+ Index = FileList->Count;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ Index++;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+void __fastcall TFTPFileSystem::CalculateFilesChecksum(const UnicodeString & Alg,
|
|
|
|
+ TStrings * FileList, TStrings * Checksums,
|
|
|
|
+ TCalculatedChecksumEvent OnCalculatedChecksum)
|
|
|
|
+{
|
|
|
|
+ TFileOperationProgressType Progress(&FTerminal->DoProgress, &FTerminal->DoFinished);
|
|
|
|
+ Progress.Start(foCalculateChecksum, osRemote, FileList->Count);
|
|
|
|
+
|
|
|
|
+ FTerminal->FOperationProgress = &Progress;
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ UnicodeString NormalizedAlg = FindIdent(FindIdent(Alg, FHashAlgs.get()), FChecksumAlgs.get());
|
|
|
|
+
|
|
|
|
+ bool UsingHashCommand = (FHashAlgs->IndexOf(NormalizedAlg) >= 0);
|
|
|
|
+ if (UsingHashCommand)
|
|
|
|
+ {
|
|
|
|
+ // The server should understand lowercase alg name by spec,
|
|
|
|
+ // but we should use uppercase anyway
|
|
|
|
+ SendCommand(FORMAT(L"OPTS %s %s", (HashCommand, UpperCase(NormalizedAlg))));
|
|
|
|
+ GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
|
|
+ }
|
|
|
|
+ else if (FChecksumAlgs->IndexOf(NormalizedAlg) >= 0)
|
|
|
|
+ {
|
|
|
|
+ // will use algorithm-specific command
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ throw Exception(FMTLOAD(UNKNOWN_CHECKSUM, (Alg)));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ DoCalculateFilesChecksum(UsingHashCommand, NormalizedAlg, FileList, Checksums, OnCalculatedChecksum,
|
|
|
|
+ &Progress, true);
|
|
|
|
+ }
|
|
|
|
+ __finally
|
|
|
|
+ {
|
|
|
|
+ FTerminal->FOperationProgress = NULL;
|
|
|
|
+ Progress.Stop();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
bool __fastcall TFTPFileSystem::ConfirmOverwrite(
|
|
bool __fastcall TFTPFileSystem::ConfirmOverwrite(
|
|
@@ -1178,13 +1474,12 @@ void __fastcall TFTPFileSystem::SinkRobust(const UnicodeString FileName,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags)
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags)
|
|
{
|
|
{
|
|
// the same in TSFTPFileSystem
|
|
// the same in TSFTPFileSystem
|
|
- bool Retry;
|
|
|
|
|
|
|
|
TDownloadSessionAction Action(FTerminal->ActionLog);
|
|
TDownloadSessionAction Action(FTerminal->ActionLog);
|
|
|
|
+ TRobustOperationLoop RobustLoop(FTerminal, OperationProgress);
|
|
|
|
|
|
do
|
|
do
|
|
{
|
|
{
|
|
- Retry = false;
|
|
|
|
try
|
|
try
|
|
{
|
|
{
|
|
Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress,
|
|
Sink(FileName, File, TargetDir, CopyParam, Params, OperationProgress,
|
|
@@ -1192,16 +1487,14 @@ void __fastcall TFTPFileSystem::SinkRobust(const UnicodeString FileName,
|
|
}
|
|
}
|
|
catch(Exception & E)
|
|
catch(Exception & E)
|
|
{
|
|
{
|
|
- Retry = true;
|
|
|
|
- if (FTerminal->Active ||
|
|
|
|
- !FTerminal->QueryReopen(&E, ropNoReadDirectory, OperationProgress))
|
|
|
|
|
|
+ if (!RobustLoop.TryReopen(E))
|
|
{
|
|
{
|
|
FTerminal->RollbackAction(Action, OperationProgress, &E);
|
|
FTerminal->RollbackAction(Action, OperationProgress, &E);
|
|
throw;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (Retry)
|
|
|
|
|
|
+ if (RobustLoop.ShouldRetry())
|
|
{
|
|
{
|
|
OperationProgress->RollbackTransfer();
|
|
OperationProgress->RollbackTransfer();
|
|
Action.Restart();
|
|
Action.Restart();
|
|
@@ -1214,7 +1507,7 @@ void __fastcall TFTPFileSystem::SinkRobust(const UnicodeString FileName,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- while (Retry);
|
|
|
|
|
|
+ while (RobustLoop.Retry());
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TFTPFileSystem::Sink(const UnicodeString FileName,
|
|
void __fastcall TFTPFileSystem::Sink(const UnicodeString FileName,
|
|
@@ -1293,8 +1586,7 @@ void __fastcall TFTPFileSystem::Sink(const UnicodeString FileName,
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- // file is symlink to directory, currently do nothing, but it should be
|
|
|
|
- // reported to user
|
|
|
|
|
|
+ FTerminal->LogEvent(FORMAT(L"Skipping symlink to directory \"%s\".", (FileName)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
else
|
|
@@ -1473,13 +1765,12 @@ void __fastcall TFTPFileSystem::SourceRobust(const UnicodeString FileName,
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags)
|
|
TFileOperationProgressType * OperationProgress, unsigned int Flags)
|
|
{
|
|
{
|
|
// the same in TSFTPFileSystem
|
|
// the same in TSFTPFileSystem
|
|
- bool Retry;
|
|
|
|
|
|
|
|
TUploadSessionAction Action(FTerminal->ActionLog);
|
|
TUploadSessionAction Action(FTerminal->ActionLog);
|
|
|
|
+ TRobustOperationLoop RobustLoop(FTerminal, OperationProgress);
|
|
|
|
|
|
do
|
|
do
|
|
{
|
|
{
|
|
- Retry = false;
|
|
|
|
try
|
|
try
|
|
{
|
|
{
|
|
Source(FileName, TargetDir, CopyParam, Params, OperationProgress,
|
|
Source(FileName, TargetDir, CopyParam, Params, OperationProgress,
|
|
@@ -1487,16 +1778,14 @@ void __fastcall TFTPFileSystem::SourceRobust(const UnicodeString FileName,
|
|
}
|
|
}
|
|
catch(Exception & E)
|
|
catch(Exception & E)
|
|
{
|
|
{
|
|
- Retry = true;
|
|
|
|
- if (FTerminal->Active ||
|
|
|
|
- !FTerminal->QueryReopen(&E, ropNoReadDirectory, OperationProgress))
|
|
|
|
|
|
+ if (!RobustLoop.TryReopen(E))
|
|
{
|
|
{
|
|
FTerminal->RollbackAction(Action, OperationProgress, &E);
|
|
FTerminal->RollbackAction(Action, OperationProgress, &E);
|
|
throw;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (Retry)
|
|
|
|
|
|
+ if (RobustLoop.ShouldRetry())
|
|
{
|
|
{
|
|
OperationProgress->RollbackTransfer();
|
|
OperationProgress->RollbackTransfer();
|
|
Action.Restart();
|
|
Action.Restart();
|
|
@@ -1506,7 +1795,7 @@ void __fastcall TFTPFileSystem::SourceRobust(const UnicodeString FileName,
|
|
Flags |= tfAutoResume;
|
|
Flags |= tfAutoResume;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- while (Retry);
|
|
|
|
|
|
+ while (RobustLoop.Retry());
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TFTPFileSystem::Source(const UnicodeString FileName,
|
|
void __fastcall TFTPFileSystem::Source(const UnicodeString FileName,
|
|
@@ -1781,13 +2070,13 @@ void __fastcall TFTPFileSystem::CreateDirectory(const UnicodeString ADirName)
|
|
void __fastcall TFTPFileSystem::CreateLink(const UnicodeString FileName,
|
|
void __fastcall TFTPFileSystem::CreateLink(const UnicodeString FileName,
|
|
const UnicodeString PointTo, bool Symbolic)
|
|
const UnicodeString PointTo, bool Symbolic)
|
|
{
|
|
{
|
|
- assert(FSupportsSiteSymlink);
|
|
|
|
|
|
+ assert(SupportsSiteCommand(SymlinkSiteCommand));
|
|
if (ALWAYS_TRUE(Symbolic))
|
|
if (ALWAYS_TRUE(Symbolic))
|
|
{
|
|
{
|
|
EnsureLocation();
|
|
EnsureLocation();
|
|
|
|
|
|
- UnicodeString Command = FORMAT(L"SITE SYMLINK %s %s", (PointTo, FileName));
|
|
|
|
- FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
|
|
+ UnicodeString Command = FORMAT(L"%s %s %s %s", (SiteCommand, SymlinkSiteCommand, PointTo, FileName));
|
|
|
|
+ SendCommand(Command);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1861,7 +2150,7 @@ void __fastcall TFTPFileSystem::DoStartup()
|
|
UnicodeString Command = PostLoginCommands->Strings[Index];
|
|
UnicodeString Command = PostLoginCommands->Strings[Index];
|
|
if (!Command.IsEmpty())
|
|
if (!Command.IsEmpty())
|
|
{
|
|
{
|
|
- FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
|
|
+ SendCommand(Command);
|
|
|
|
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_3XX_CODE);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_3XX_CODE);
|
|
}
|
|
}
|
|
@@ -1907,17 +2196,21 @@ bool __fastcall TFTPFileSystem::IsCapable(int Capability) const
|
|
return (FServerCapabilities->GetCapability(mfmt_command) == yes);
|
|
return (FServerCapabilities->GetCapability(mfmt_command) == yes);
|
|
|
|
|
|
case fcRemoteCopy:
|
|
case fcRemoteCopy:
|
|
- return FSupportsSiteCopy;
|
|
|
|
|
|
+ return SupportsSiteCommand(CopySiteCommand);
|
|
|
|
|
|
case fcSymbolicLink:
|
|
case fcSymbolicLink:
|
|
- return FSupportsSiteSymlink;
|
|
|
|
|
|
+ return SupportsSiteCommand(SymlinkSiteCommand);
|
|
|
|
+
|
|
|
|
+ case fcCalculatingChecksum:
|
|
|
|
+ return FSupportsAnyChecksumFeature;
|
|
|
|
+
|
|
|
|
+ case fcCheckingSpaceAvailable:
|
|
|
|
+ return SupportsCommand(AvblCommand) || SupportsCommand(XQuotaCommand);
|
|
|
|
|
|
case fcModeChangingUpload:
|
|
case fcModeChangingUpload:
|
|
case fcLoadingAdditionalProperties:
|
|
case fcLoadingAdditionalProperties:
|
|
case fcShellAnyCommand:
|
|
case fcShellAnyCommand:
|
|
- case fcCalculatingChecksum:
|
|
|
|
case fcHardLink:
|
|
case fcHardLink:
|
|
- case fcCheckingSpaceAvailable:
|
|
|
|
case fcUserGroupListing:
|
|
case fcUserGroupListing:
|
|
case fcGroupChanging:
|
|
case fcGroupChanging:
|
|
case fcOwnerChanging:
|
|
case fcOwnerChanging:
|
|
@@ -1948,7 +2241,8 @@ void __fastcall TFTPFileSystem::ReadCurrentDirectory()
|
|
// directory anyway, see comments in EnsureLocation
|
|
// directory anyway, see comments in EnsureLocation
|
|
if (FCurrentDirectory.IsEmpty())
|
|
if (FCurrentDirectory.IsEmpty())
|
|
{
|
|
{
|
|
- FFileZillaIntf->CustomCommand(L"PWD");
|
|
|
|
|
|
+ UnicodeString Command = L"PWD";
|
|
|
|
+ SendCommand(Command);
|
|
|
|
|
|
unsigned int Code;
|
|
unsigned int Code;
|
|
TStrings * Response = NULL;
|
|
TStrings * Response = NULL;
|
|
@@ -1999,7 +2293,7 @@ void __fastcall TFTPFileSystem::ReadCurrentDirectory()
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- throw Exception(FMTLOAD(FTP_PWD_RESPONSE_ERROR, (Response->Text)));
|
|
|
|
|
|
+ throw Exception(FMTLOAD(FTP_RESPONSE_ERROR, (Command, Response->Text)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
__finally
|
|
@@ -2048,7 +2342,10 @@ bool __fastcall TFTPFileSystem::TimeZoneDifferenceApplicable(TModificationFmt Mo
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TFTPFileSystem::ApplyTimeDifference(TRemoteFile * File)
|
|
void __fastcall TFTPFileSystem::ApplyTimeDifference(TRemoteFile * File)
|
|
{
|
|
{
|
|
- if (TimeZoneDifferenceApplicable(File->ModificationFmt))
|
|
|
|
|
|
+ // FTimeDifference is not only optimization, but also prevents assertion failing
|
|
|
|
+ // in TimeZoneDifferenceApplicable when the file has full precision
|
|
|
|
+ if ((FTimeDifference != 0) &&
|
|
|
|
+ TimeZoneDifferenceApplicable(File->ModificationFmt))
|
|
{
|
|
{
|
|
assert(File->Modification == File->LastAccess);
|
|
assert(File->Modification == File->LastAccess);
|
|
File->Modification = IncSecond(File->Modification, FTimeDifference);
|
|
File->Modification = IncSecond(File->Modification, FTimeDifference);
|
|
@@ -2197,6 +2494,8 @@ void __fastcall TFTPFileSystem::DoReadFile(const UnicodeString & AFileName,
|
|
TRemoteFileList * FileList = new TRemoteFileList();
|
|
TRemoteFileList * FileList = new TRemoteFileList();
|
|
try
|
|
try
|
|
{
|
|
{
|
|
|
|
+ // Duplicate() call below would use this to compose FullFileName
|
|
|
|
+ FileList->Directory = FilePath;
|
|
TFileListHelper Helper(this, FileList, false);
|
|
TFileListHelper Helper(this, FileList, false);
|
|
FFileZillaIntf->ListFile(FileNameOnly.c_str(), FilePath.c_str());
|
|
FFileZillaIntf->ListFile(FileNameOnly.c_str(), FilePath.c_str());
|
|
|
|
|
|
@@ -2334,17 +2633,17 @@ void __fastcall TFTPFileSystem::RenameFile(const UnicodeString AFileName,
|
|
void __fastcall TFTPFileSystem::CopyFile(const UnicodeString FileName,
|
|
void __fastcall TFTPFileSystem::CopyFile(const UnicodeString FileName,
|
|
const UnicodeString NewName)
|
|
const UnicodeString NewName)
|
|
{
|
|
{
|
|
- assert(FSupportsSiteCopy);
|
|
|
|
|
|
+ assert(SupportsSiteCommand(CopySiteCommand));
|
|
EnsureLocation();
|
|
EnsureLocation();
|
|
|
|
|
|
UnicodeString Command;
|
|
UnicodeString Command;
|
|
|
|
|
|
- Command = FORMAT(L"SITE CPFR %s", (FileName));
|
|
|
|
- FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
|
|
+ Command = FORMAT(L"%s CPFR %s", (SiteCommand, FileName));
|
|
|
|
+ SendCommand(Command);
|
|
GotReply(WaitForCommandReply(), REPLY_3XX_CODE);
|
|
GotReply(WaitForCommandReply(), REPLY_3XX_CODE);
|
|
|
|
|
|
- Command = FORMAT(L"SITE CPTO %s", (NewName));
|
|
|
|
- FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
|
|
+ Command = FORMAT(L"%s CPTO %s", (SiteCommand, NewName));
|
|
|
|
+ SendCommand(Command);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
@@ -2353,10 +2652,57 @@ TStrings * __fastcall TFTPFileSystem::GetFixedPaths()
|
|
return NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
-void __fastcall TFTPFileSystem::SpaceAvailable(const UnicodeString /*Path*/,
|
|
|
|
- TSpaceAvailable & /*ASpaceAvailable*/)
|
|
|
|
|
|
+void __fastcall TFTPFileSystem::SpaceAvailable(const UnicodeString Path,
|
|
|
|
+ TSpaceAvailable & ASpaceAvailable)
|
|
{
|
|
{
|
|
- FAIL;
|
|
|
|
|
|
+ if (SupportsCommand(XQuotaCommand))
|
|
|
|
+ {
|
|
|
|
+ // WS_FTP:
|
|
|
|
+ // XQUOTA
|
|
|
|
+ // 213-File and disk usage
|
|
|
|
+ // File count: 3
|
|
|
|
+ // File limit: 10000
|
|
|
|
+ // Disk usage: 1532791
|
|
|
|
+ // Disk limit: 2048000
|
|
|
|
+ // 213 File and disk usage end
|
|
|
|
+
|
|
|
|
+ // XQUOTA is global not path-specific
|
|
|
|
+ UnicodeString Command = XQuotaCommand;
|
|
|
|
+ SendCommand(Command);
|
|
|
|
+ TStrings * Response = NULL;
|
|
|
|
+ GotReply(WaitForCommandReply(), REPLY_2XX_CODE, L"", NULL, &Response);
|
|
|
|
+ std::unique_ptr<TStrings> ResponseOwner(Response);
|
|
|
|
+
|
|
|
|
+ __int64 UsedBytes = -1;
|
|
|
|
+ for (int Index = 0; Index < Response->Count; Index++)
|
|
|
|
+ {
|
|
|
|
+ // trimming padding
|
|
|
|
+ UnicodeString Line = Trim(Response->Strings[Index]);
|
|
|
|
+ UnicodeString Label = CutToChar(Line, L':', true);
|
|
|
|
+ if (SameText(Label, L"Disk usage"))
|
|
|
|
+ {
|
|
|
|
+ UsedBytes = StrToInt64(Line);
|
|
|
|
+ }
|
|
|
|
+ else if (SameText(Label, L"Disk limit") && !SameText(Line, L"unlimited"))
|
|
|
|
+ {
|
|
|
|
+ ASpaceAvailable.BytesAvailableToUser = StrToInt64(Line);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ((UsedBytes >= 0) && (ASpaceAvailable.BytesAvailableToUser > 0))
|
|
|
|
+ {
|
|
|
|
+ ASpaceAvailable.UnusedBytesAvailableToUser = ASpaceAvailable.BytesAvailableToUser - UsedBytes;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SupportsCommand(AvblCommand))
|
|
|
|
+ {
|
|
|
|
+ // draft-peterson-streamlined-ftp-command-extensions-10
|
|
|
|
+ // Implemented by Serv-U.
|
|
|
|
+ UnicodeString Command = FORMAT(L"%s %s", (AvblCommand, Path));
|
|
|
|
+ SendCommand(Command);
|
|
|
|
+ UnicodeString Response = GotReply(WaitForCommandReply(), REPLY_2XX_CODE | REPLY_SINGLE_LINE);
|
|
|
|
+ ASpaceAvailable.UnusedBytesAvailableToUser = StrToInt64(Response);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
const TSessionInfo & __fastcall TFTPFileSystem::GetSessionInfo()
|
|
const TSessionInfo & __fastcall TFTPFileSystem::GetSessionInfo()
|
|
@@ -2381,7 +2727,8 @@ const TFileSystemInfo & __fastcall TFTPFileSystem::GetFileSystemInfo(bool /*Retr
|
|
FORMAT(L"%s\r\n", (LoadStr(FTP_FEATURE_INFO)));
|
|
FORMAT(L"%s\r\n", (LoadStr(FTP_FEATURE_INFO)));
|
|
for (int Index = 0; Index < FFeatures->Count; Index++)
|
|
for (int Index = 0; Index < FFeatures->Count; Index++)
|
|
{
|
|
{
|
|
- FFileSystemInfo.AdditionalInfo += FORMAT(L" %s\r\n", (FFeatures->Strings[Index]));
|
|
|
|
|
|
+ // For TrimLeft, refer to HandleFeatReply
|
|
|
|
+ FFileSystemInfo.AdditionalInfo += FORMAT(L" %s\r\n", (TrimLeft(FFeatures->Strings[Index])));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2587,6 +2934,14 @@ int __fastcall TFTPFileSystem::GetOptionVal(int OptionID) const
|
|
Result = FTerminal->Configuration->LogSensitive ? TRUE : FALSE;
|
|
Result = FTerminal->Configuration->LogSensitive ? TRUE : FALSE;
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
+ case OPTION_MPEXT_HOST:
|
|
|
|
+ Result = (Data->FtpHost == asOn);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case OPTION_MPEXT_NODELAY:
|
|
|
|
+ Result = Data->TcpNoDelay;
|
|
|
|
+ break;
|
|
|
|
+
|
|
default:
|
|
default:
|
|
FAIL;
|
|
FAIL;
|
|
Result = FALSE;
|
|
Result = FALSE;
|
|
@@ -2810,9 +3165,10 @@ void __fastcall TFTPFileSystem::GotNonCommandReply(unsigned int Reply)
|
|
FAIL;
|
|
FAIL;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
-void __fastcall TFTPFileSystem::GotReply(unsigned int Reply, unsigned int Flags,
|
|
|
|
|
|
+UnicodeString __fastcall TFTPFileSystem::GotReply(unsigned int Reply, unsigned int Flags,
|
|
UnicodeString Error, unsigned int * Code, TStrings ** Response)
|
|
UnicodeString Error, unsigned int * Code, TStrings ** Response)
|
|
{
|
|
{
|
|
|
|
+ UnicodeString Result;
|
|
try
|
|
try
|
|
{
|
|
{
|
|
if (FLAGSET(Reply, TFileZillaIntf::REPLY_OK))
|
|
if (FLAGSET(Reply, TFileZillaIntf::REPLY_OK))
|
|
@@ -2897,6 +3253,7 @@ void __fastcall TFTPFileSystem::GotReply(unsigned int Reply, unsigned int Flags,
|
|
|
|
|
|
if (FLastCode == 530)
|
|
if (FLastCode == 530)
|
|
{
|
|
{
|
|
|
|
+ // Serv-U also uses this code in response to "SITE PSWD"
|
|
MoreMessages->Add(LoadStr(AUTHENTICATION_FAILED));
|
|
MoreMessages->Add(LoadStr(AUTHENTICATION_FAILED));
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2970,6 +3327,15 @@ void __fastcall TFTPFileSystem::GotReply(unsigned int Reply, unsigned int Flags,
|
|
*Code = FLastCode;
|
|
*Code = FLastCode;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (FLAGSET(Flags, REPLY_SINGLE_LINE))
|
|
|
|
+ {
|
|
|
|
+ if (FLastResponse->Count != 1)
|
|
|
|
+ {
|
|
|
|
+ throw Exception(FMTLOAD(FTP_RESPONSE_ERROR, (FLastCommandSent, FLastResponse->Text)));
|
|
|
|
+ }
|
|
|
|
+ Result = FLastResponse->Strings[0];
|
|
|
|
+ }
|
|
|
|
+
|
|
if (Response != NULL)
|
|
if (Response != NULL)
|
|
{
|
|
{
|
|
*Response = FLastResponse;
|
|
*Response = FLastResponse;
|
|
@@ -2983,6 +3349,14 @@ void __fastcall TFTPFileSystem::GotReply(unsigned int Reply, unsigned int Flags,
|
|
{
|
|
{
|
|
ResetReply();
|
|
ResetReply();
|
|
}
|
|
}
|
|
|
|
+ return Result;
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+void __fastcall TFTPFileSystem::SendCommand(const UnicodeString & Command)
|
|
|
|
+{
|
|
|
|
+ FFileZillaIntf->CustomCommand(Command.c_str());
|
|
|
|
+ int From = 1;
|
|
|
|
+ FLastCommandSent = CopyToChars(Command, From, L" ", false);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TFTPFileSystem::SetLastCode(int Code)
|
|
void __fastcall TFTPFileSystem::SetLastCode(int Code)
|
|
@@ -3027,6 +3401,13 @@ void __fastcall TFTPFileSystem::HandleReplyStatus(UnicodeString Response)
|
|
// 211-PROT
|
|
// 211-PROT
|
|
// 211 End
|
|
// 211 End
|
|
|
|
|
|
|
|
+ // IIS 2003:
|
|
|
|
+
|
|
|
|
+ // 211-FEAT
|
|
|
|
+ // SIZE
|
|
|
|
+ // MDTM
|
|
|
|
+ // 211 END
|
|
|
|
+
|
|
bool HasCodePrefix =
|
|
bool HasCodePrefix =
|
|
(Response.Length() >= 3) &&
|
|
(Response.Length() >= 3) &&
|
|
TryStrToInt(Response.SubString(1, 3), Code) &&
|
|
TryStrToInt(Response.SubString(1, 3), Code) &&
|
|
@@ -3073,10 +3454,20 @@ void __fastcall TFTPFileSystem::HandleReplyStatus(UnicodeString Response)
|
|
{
|
|
{
|
|
if (FLastCode == 220)
|
|
if (FLastCode == 220)
|
|
{
|
|
{
|
|
- FWelcomeMessage = FLastResponse->Text;
|
|
|
|
- if (FTerminal->Configuration->ShowFtpWelcomeMessage)
|
|
|
|
|
|
+ // HOST command also uses 220 response.
|
|
|
|
+ // Neither our use of welcome messagfe is prepared for changing it
|
|
|
|
+ // during the session, so we keep the initial message only.
|
|
|
|
+ // Theoretically the welcome message can be host-specific,
|
|
|
|
+ // but IIS uses "220 Host accepted", and we are not interested in that anyway.
|
|
|
|
+ // Serv-U repeats the initial welcome message.
|
|
|
|
+ // WS_FTP uses "200 Command HOST succeed"
|
|
|
|
+ if (FWelcomeMessage.IsEmpty())
|
|
{
|
|
{
|
|
- FTerminal->DisplayBanner(FWelcomeMessage);
|
|
|
|
|
|
+ FWelcomeMessage = FLastResponse->Text;
|
|
|
|
+ if (FTerminal->Configuration->ShowFtpWelcomeMessage)
|
|
|
|
+ {
|
|
|
|
+ FTerminal->DisplayBanner(FWelcomeMessage);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (FLastCommand == PASS)
|
|
else if (FLastCommand == PASS)
|
|
@@ -3109,28 +3500,78 @@ void __fastcall TFTPFileSystem::HandleReplyStatus(UnicodeString Response)
|
|
}
|
|
}
|
|
else if (FLastCommand == FEAT)
|
|
else if (FLastCommand == FEAT)
|
|
{
|
|
{
|
|
- // Response to FEAT must be multiline, where leading and trailing line
|
|
|
|
- // is "meaningless". See RFC 2389.
|
|
|
|
- if ((FLastCode == 211) && (FLastResponse->Count > 2))
|
|
|
|
|
|
+ HandleFeatReply();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+void __fastcall TFTPFileSystem::ResetFeatures()
|
|
|
|
+{
|
|
|
|
+ FFeatures->Clear();
|
|
|
|
+ FSupportedCommands->Clear();
|
|
|
|
+ FSupportedSiteCommands->Clear();
|
|
|
|
+ FHashAlgs->Clear();
|
|
|
|
+ FSupportsAnyChecksumFeature = false;
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+void __fastcall TFTPFileSystem::HandleFeatReply()
|
|
|
|
+{
|
|
|
|
+ ResetFeatures();
|
|
|
|
+ // Response to FEAT must be multiline, where leading and trailing line
|
|
|
|
+ // is "meaningless". See RFC 2389.
|
|
|
|
+ if ((FLastCode == 211) && (FLastResponse->Count > 2))
|
|
|
|
+ {
|
|
|
|
+ FLastResponse->Delete(0);
|
|
|
|
+ FLastResponse->Delete(FLastResponse->Count - 1);
|
|
|
|
+ FFeatures->Assign(FLastResponse);
|
|
|
|
+ for (int Index = 0; Index < FFeatures->Count; Index++)
|
|
|
|
+ {
|
|
|
|
+ // IIS 2003 indents response by 4 spaces, instead of one,
|
|
|
|
+ // see example in HandleReplyStatus
|
|
|
|
+ UnicodeString Feature = TrimLeft(FFeatures->Strings[Index]);
|
|
|
|
+
|
|
|
|
+ UnicodeString Args = Feature;
|
|
|
|
+ UnicodeString Command = CutToChar(Args, L' ', true);
|
|
|
|
+
|
|
|
|
+ // Serv-U lists Xalg commands like:
|
|
|
|
+ // XSHA1 filename;start;end
|
|
|
|
+ FSupportedCommands->Add(Command);
|
|
|
|
+
|
|
|
|
+ if (SameText(Command, SiteCommand))
|
|
{
|
|
{
|
|
- FLastResponse->Delete(0);
|
|
|
|
- FLastResponse->Delete(FLastResponse->Count - 1);
|
|
|
|
- FFeatures->Assign(FLastResponse);
|
|
|
|
- for (int Index = 0; Index < FFeatures->Count; Index++)
|
|
|
|
|
|
+ // Serv-U lists all SITE commands in one line like:
|
|
|
|
+ // SITE PSWD;SET;ZONE;CHMOD;MSG;EXEC;HELP
|
|
|
|
+ // But ProFTPD lists them separatelly:
|
|
|
|
+ // SITE UTIME
|
|
|
|
+ // SITE RMDIR
|
|
|
|
+ // SITE COPY
|
|
|
|
+ // SITE MKDIR
|
|
|
|
+ // SITE SYMLINK
|
|
|
|
+ while (!Args.IsEmpty())
|
|
{
|
|
{
|
|
- if (SameText(FFeatures->Strings[Index], L"SITE COPY"))
|
|
|
|
- {
|
|
|
|
- FSupportsSiteCopy = true;
|
|
|
|
- }
|
|
|
|
- else if (SameText(FFeatures->Strings[Index], L"SITE SYMLINK"))
|
|
|
|
|
|
+ UnicodeString Arg = CutToChar(Args, L';', true);
|
|
|
|
+ FSupportedSiteCommands->Add(Arg);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (SameText(Command, HashCommand))
|
|
|
|
+ {
|
|
|
|
+ while (!Args.IsEmpty())
|
|
|
|
+ {
|
|
|
|
+ UnicodeString Alg = CutToChar(Args, L';', true);
|
|
|
|
+ if ((Alg.Length() > 0) && (Alg[Alg.Length()] == L'*'))
|
|
{
|
|
{
|
|
- FSupportsSiteSymlink = true;
|
|
|
|
|
|
+ Alg.Delete(Alg.Length(), 1);
|
|
}
|
|
}
|
|
|
|
+ // FTP HASH alg names follow IANA as we do,
|
|
|
|
+ // but using uppercase and we use lowercase
|
|
|
|
+ FHashAlgs->Add(LowerCase(Alg));
|
|
|
|
+ FSupportsAnyChecksumFeature = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+
|
|
|
|
+ if (FChecksumCommands->IndexOf(Command) >= 0)
|
|
{
|
|
{
|
|
- FFeatures->Clear();
|
|
|
|
|
|
+ FSupportsAnyChecksumFeature = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -4098,9 +4539,39 @@ bool __fastcall TFTPFileSystem::GetFileModificationTimeInUtc(const wchar_t * Fil
|
|
return Result;
|
|
return Result;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
-void __fastcall TFTPFileSystem::GetSupportedChecksumAlgs(TStrings * /*Algs*/)
|
|
|
|
|
|
+void __fastcall TFTPFileSystem::RegisterChecksumAlgCommand(const UnicodeString & Alg, const UnicodeString & Command)
|
|
|
|
+{
|
|
|
|
+ FChecksumAlgs->Add(Alg);
|
|
|
|
+ FChecksumCommands->Add(Command);
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+void __fastcall TFTPFileSystem::GetSupportedChecksumAlgs(TStrings * Algs)
|
|
|
|
+{
|
|
|
|
+ for (int Index = 0; Index < FHashAlgs->Count; Index++)
|
|
|
|
+ {
|
|
|
|
+ Algs->Add(FHashAlgs->Strings[Index]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (int Index = 0; Index < FChecksumAlgs->Count; Index++)
|
|
|
|
+ {
|
|
|
|
+ UnicodeString Alg = FChecksumAlgs->Strings[Index];
|
|
|
|
+ UnicodeString Command = FChecksumCommands->Strings[Index];
|
|
|
|
+
|
|
|
|
+ if (SupportsCommand(Command) && (Algs->IndexOf(Alg) < 0))
|
|
|
|
+ {
|
|
|
|
+ Algs->Add(Alg);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+bool __fastcall TFTPFileSystem::SupportsSiteCommand(const UnicodeString & Command) const
|
|
|
|
+{
|
|
|
|
+ return (FSupportedSiteCommands->IndexOf(Command) >= 0);
|
|
|
|
+}
|
|
|
|
+//---------------------------------------------------------------------------
|
|
|
|
+bool __fastcall TFTPFileSystem::SupportsCommand(const UnicodeString & Command) const
|
|
{
|
|
{
|
|
- // NOOP
|
|
|
|
|
|
+ return (FSupportedCommands->IndexOf(Command) >= 0);
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------
|
|
UnicodeString __fastcall GetOpenSSLVersionText()
|
|
UnicodeString __fastcall GetOpenSSLVersionText()
|