瀏覽代碼

Bug 49: Improved FTP support for VMS servers (and potentially for other non-Unix-like systems)

https://winscp.net/tracker/49
(cherry picked from commit 3dbbb76f9289d6114bccbe70d9405c5946782256)

Source commit: ad711d5d4ce0357fad93967b93664c8111afc07a
Martin Prikryl 4 年之前
父節點
當前提交
b8fd5b18a2
共有 5 個文件被更改,包括 115 次插入29 次删除
  1. 98 20
      source/core/FtpFileSystem.cpp
  2. 4 0
      source/core/FtpFileSystem.h
  3. 6 6
      source/core/SessionData.cpp
  4. 3 3
      source/core/SessionData.h
  5. 4 0
      source/core/SessionInfo.cpp

+ 98 - 20
source/core/FtpFileSystem.cpp

@@ -257,6 +257,7 @@ __fastcall TFTPFileSystem::TFTPFileSystem(TTerminal * ATerminal):
   ResetReply();
 
   FListAll = FTerminal->SessionData->FtpListAll;
+  FWorkFromCwd = FTerminal->SessionData->FtpWorkFromCwd;
   FFileSystemInfo.ProtocolBaseName = L"FTP";
   FFileSystemInfo.ProtocolName = FFileSystemInfo.ProtocolBaseName;
   FTimeoutStatus = LoadStr(IDS_ERRORMSG_TIMEOUT);
@@ -902,6 +903,16 @@ void __fastcall TFTPFileSystem::EnsureLocation()
   }
 }
 //---------------------------------------------------------------------------
+bool TFTPFileSystem::EnsureLocationWhenWorkFromCwd(const UnicodeString & Directory)
+{
+  bool Result = (FWorkFromCwd == asOn);
+  if (Result)
+  {
+    EnsureLocation(Directory, false);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall TFTPFileSystem::AnyCommand(const UnicodeString Command,
   TCaptureOutputEvent OutputEvent)
 {
@@ -934,7 +945,7 @@ void __fastcall TFTPFileSystem::AnnounceFileListOperation()
   ResetCaches();
 }
 //---------------------------------------------------------------------------
-void __fastcall TFTPFileSystem::DoChangeDirectory(const UnicodeString & Directory)
+void TFTPFileSystem::SendCwd(const UnicodeString & Directory)
 {
   UnicodeString Command = FORMAT(L"CWD %s", (Directory));
   SendCommand(Command);
@@ -942,6 +953,41 @@ void __fastcall TFTPFileSystem::DoChangeDirectory(const UnicodeString & Director
   GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
 }
 //---------------------------------------------------------------------------
+void __fastcall TFTPFileSystem::DoChangeDirectory(const UnicodeString & Directory)
+{
+  if (FWorkFromCwd == asOn)
+  {
+    UnicodeString ADirectory = UnixIncludeTrailingBackslash(AbsolutePath(Directory, false));
+
+    UnicodeString Actual = UnixIncludeTrailingBackslash(ActualCurrentDirectory());
+    while (!UnixSamePath(Actual, ADirectory))
+    {
+      if (UnixIsChildPath(Actual, ADirectory))
+      {
+        UnicodeString SubDirectory = UnixExcludeTrailingBackslash(ADirectory);
+        SubDirectory.Delete(1, Actual.Length());
+        int P = SubDirectory.Pos(L'/');
+        if (P > 0)
+        {
+          SubDirectory.SetLength(P - 1);
+        }
+        SendCwd(SubDirectory);
+        Actual = UnixIncludeTrailingBackslash(Actual + SubDirectory);
+      }
+      else
+      {
+        SendCommand(L"CDUP");
+        GotReply(WaitForCommandReply(), REPLY_2XX_CODE);
+        Actual = UnixExtractFilePath(UnixExcludeTrailingBackslash(Actual));
+      }
+    }
+  }
+  else
+  {
+    SendCwd(Directory);
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TFTPFileSystem::ChangeDirectory(const UnicodeString ADirectory)
 {
   UnicodeString Directory = ADirectory;
@@ -1033,7 +1079,7 @@ void __fastcall TFTPFileSystem::ChangeFileProperties(const UnicodeString AFileNa
       Action.Rights(Rights);
 
       UnicodeString FileNameOnly = UnixExtractFileName(FileName);
-      UnicodeString FilePath = UnixExtractFilePath(FileName);
+      UnicodeString FilePath = RemoteExtractFilePath(FileName);
       // FZAPI wants octal number represented as decadic
       FFileZillaIntf->Chmod(Rights.NumberDecadic, FileNameOnly.c_str(), FilePath.c_str());
 
@@ -1542,6 +1588,23 @@ void __fastcall TFTPFileSystem::CopyToLocal(TStrings * FilesToCopy,
     FilesToCopy, TargetDir, CopyParam, Params, OperationProgress, tfUseFileTransferAny, OnceDoneOperation);
 }
 //---------------------------------------------------------------------------
+UnicodeString TFTPFileSystem::RemoteExtractFilePath(const UnicodeString & Path)
+{
+  UnicodeString Result;
+  // If the path ends with a slash, FZAPI CServerPath contructor does not identify the path as VMS.
+  // It is probably ok to use UnixExtractFileDir for all paths passed to FZAPI,
+  // but for now, we limit the impact of the change to VMS.
+  if (FVMS)
+  {
+    Result = UnixExtractFileDir(Path);
+  }
+  else
+  {
+    Result = UnixExtractFilePath(Path);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall TFTPFileSystem::Sink(
   const UnicodeString & FileName, const TRemoteFile * File,
   const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
@@ -1555,9 +1618,11 @@ void __fastcall TFTPFileSystem::Sink(
   TFileTransferData UserData;
 
   UnicodeString DestFullName = TargetDir + DestFileName;
-  UnicodeString FilePath = UnixExtractFilePath(FileName);
+  UnicodeString FilePath = RemoteExtractFilePath(FileName);
   unsigned int TransferType = (OperationProgress->AsciiTransfer ? 1 : 2);
 
+  EnsureLocationWhenWorkFromCwd(FilePath);
+
   {
     // ignore file list
     TFileListHelper Helper(this, NULL, true);
@@ -1634,6 +1699,8 @@ void __fastcall TFTPFileSystem::Source(
 
   unsigned int TransferType = (OperationProgress->AsciiTransfer ? 1 : 2);
 
+  EnsureLocationWhenWorkFromCwd(TargetDir);
+
   {
     // ignore file list
     TFileListHelper Helper(this, NULL, true);
@@ -1717,7 +1784,7 @@ void __fastcall TFTPFileSystem::DeleteFile(const UnicodeString AFileName,
 {
   UnicodeString FileName = AbsolutePath(AFileName, false);
   UnicodeString FileNameOnly = UnixExtractFileName(FileName);
-  UnicodeString FilePath = UnixExtractFilePath(FileName);
+  UnicodeString FilePath = RemoteExtractFilePath(FileName);
 
   bool Dir = FTerminal->DeleteContentsIfDirectory(FileName, File, Params, Action);
 
@@ -1742,10 +1809,8 @@ void __fastcall TFTPFileSystem::DeleteFile(const UnicodeString AFileName,
     }
     else
     {
-      if ((FTerminal->SessionData->FtpDeleteFromCwd == asOn) ||
-          ((FTerminal->SessionData->FtpDeleteFromCwd == asAuto) && FVMS))
+      if (EnsureLocationWhenWorkFromCwd(FilePath))
       {
-        EnsureLocation(FilePath, false);
         FFileZillaIntf->Delete(FileNameOnly.c_str(), L"", true);
       }
       else
@@ -1946,6 +2011,8 @@ void __fastcall TFTPFileSystem::ReadCurrentDirectory()
 //---------------------------------------------------------------------------
 void __fastcall TFTPFileSystem::DoReadDirectory(TRemoteFileList * FileList)
 {
+  EnsureLocationWhenWorkFromCwd(FileList->Directory);
+
   FBytesAvailable = -1;
   FileList->Reset();
   // FZAPI does not list parent directory, add it
@@ -2264,7 +2331,7 @@ void __fastcall TFTPFileSystem::DoReadFile(const UnicodeString & AFileName,
   else
   {
     FileNameOnly = UnixExtractFileName(FileName);
-    FilePath = UnixExtractFilePath(FileName);
+    FilePath = RemoteExtractFilePath(FileName);
   }
 
   TRemoteFileList * FileList = new TRemoteFileList();
@@ -2316,7 +2383,7 @@ void __fastcall TFTPFileSystem::ReadFile(const UnicodeString FileName,
     }
     else
     {
-      UnicodeString Path = UnixExtractFilePath(FileName);
+      UnicodeString Path = RemoteExtractFilePath(FileName);
       UnicodeString NameOnly;
       int P;
       bool MVSPath =
@@ -2421,9 +2488,9 @@ void __fastcall TFTPFileSystem::RenameFile(const UnicodeString AFileName, const
   UnicodeString NewName = AbsolutePath(ANewName, false);
 
   UnicodeString FileNameOnly = UnixExtractFileName(FileName);
-  UnicodeString FilePathOnly = UnixExtractFilePath(FileName);
+  UnicodeString FilePathOnly = RemoteExtractFilePath(FileName);
   UnicodeString NewNameOnly = UnixExtractFileName(NewName);
-  UnicodeString NewPathOnly = UnixExtractFilePath(NewName);
+  UnicodeString NewPathOnly = RemoteExtractFilePath(NewName);
 
   {
     // ignore file list
@@ -3367,30 +3434,41 @@ void __fastcall TFTPFileSystem::HandleReplyStatus(UnicodeString Response)
     else if (FLastCommand == SYST)
     {
       DebugAssert(FSystem.IsEmpty());
-      // Positive reply to "SYST" must be 215, see RFC 959
-      if (FLastCode == 215)
+      // Positive reply to "SYST" should be 215, see RFC 959.
+      // But "VMS VAX/VMS V6.1 on node nsrp14" uses plain 200.
+      if (FLastCodeClass == 2)
       {
         FSystem = FLastResponse->Text.TrimRight();
         // full name is "MVS is the operating system of this server. FTP Server is running on ..."
         // (the ... can be "z/OS")
         // https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.cs3cod0/ftp215-02.htm
         FMVS = (FSystem.SubString(1, 3) == L"MVS");
+        // The FWelcomeMessage usually contains "Microsoft FTP Service" but can be empty
+        if (ContainsText(FSystem, L"Windows_NT"))
+        {
+          FTerminal->LogEvent(L"The server is probably running Windows, assuming that directory listing timestamps are affected by DST.");
+          FWindowsServer = true;
+        }
+        // VMS system type. VMS V5.5-2.
+        // VMS VAX/VMS V6.1 on node nsrp14
+        if (FSystem.SubString(1, 3) == L"VMS")
+        {
+          FTerminal->LogEvent(L"VMS system detected.");
+          FVMS = true;
+        }
         if ((FListAll == asAuto) &&
              // full name is "Personal FTP Server PRO K6.0"
             ((FSystem.Pos(L"Personal FTP Server") > 0) ||
-             FMVS))
+             FMVS || FVMS))
         {
           FTerminal->LogEvent(L"Server is known not to support LIST -a");
           FListAll = asOff;
         }
-        // The FWelcomeMessage usually contains "Microsoft FTP Service" but can be empty
-        if (ContainsText(FSystem, L"Windows_NT"))
+        if ((FWorkFromCwd == asAuto) && FVMS)
         {
-          FTerminal->LogEvent(L"The server is probably running Windows, assuming that directory listing timestamps are affected by DST.");
-          FWindowsServer = true;
+          FTerminal->LogEvent(L"Server is known to require use of relative paths");
+          FWorkFromCwd = asOn;
         }
-        // VMS system type. VMS V5.5-2.
-        FVMS = (FSystem.SubString(1, 3) == L"VMS");
       }
       else
       {

+ 4 - 0
source/core/FtpFileSystem.h

@@ -153,9 +153,11 @@ protected:
   bool __fastcall GetFileModificationTimeInUtc(const wchar_t * FileName, struct tm & Time);
   void __fastcall EnsureLocation(const UnicodeString & Directory, bool Log);
   void __fastcall EnsureLocation();
+  bool EnsureLocationWhenWorkFromCwd(const UnicodeString & Directory);
   UnicodeString __fastcall ActualCurrentDirectory();
   void __fastcall Discard();
   void __fastcall DoChangeDirectory(const UnicodeString & Directory);
+  void SendCwd(const UnicodeString & Directory);
 
   void __fastcall Sink(const UnicodeString FileName,
     const TRemoteFile * File, const UnicodeString TargetDir,
@@ -212,6 +214,7 @@ protected:
   void __fastcall SendCommand(const UnicodeString & Command);
   bool __fastcall CanTransferSkipList(int Params, unsigned int Flags, const TCopyParamType * CopyParam);
   void __fastcall Disconnect();
+  UnicodeString RemoteExtractFilePath(const UnicodeString & Path);
 
   static bool __fastcall Unquote(UnicodeString & Str);
 
@@ -271,6 +274,7 @@ private:
   UnicodeString FUserName;
   TAutoSwitch FListAll;
   bool FDoListAll;
+  TAutoSwitch FWorkFromCwd;
   TFTPServerCapabilities * FServerCapabilities;
   TDateTime FLastDataSent;
   bool FAnyTransferSucceeded;

+ 6 - 6
source/core/SessionData.cpp

@@ -275,7 +275,7 @@ void __fastcall TSessionData::DefaultSettings()
   MaxTlsVersion = tls13;
   FtpListAll = asAuto;
   FtpHost = asAuto;
-  FtpDeleteFromCwd = asAuto;
+  FtpWorkFromCwd = asAuto;
   SslSessionReuse = true;
   TlsCertificateFile = L"";
 
@@ -446,7 +446,7 @@ void __fastcall TSessionData::NonPersistant()
   PROPERTY(FtpTransferActiveImmediately); \
   PROPERTY(FtpListAll); \
   PROPERTY(FtpHost); \
-  PROPERTY(FtpDeleteFromCwd); \
+  PROPERTY(FtpWorkFromCwd); \
   PROPERTY(SslSessionReuse); \
   PROPERTY(TlsCertificateFile); \
   \
@@ -845,7 +845,7 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
   Ftps = static_cast<TFtps>(Storage->ReadInteger(L"Ftps", Ftps));
   FtpListAll = TAutoSwitch(Storage->ReadInteger(L"FtpListAll", FtpListAll));
   FtpHost = TAutoSwitch(Storage->ReadInteger(L"FtpHost", FtpHost));
-  FtpDeleteFromCwd = TAutoSwitch(Storage->ReadInteger(L"FtpDeleteFromCwd", FtpDeleteFromCwd));
+  FtpWorkFromCwd = TAutoSwitch(Storage->ReadInteger(L"FtpWorkFromCwd", Storage->ReadInteger(L"FtpDeleteFromCwd", FtpWorkFromCwd)));
   SslSessionReuse = Storage->ReadBool(L"SslSessionReuse", SslSessionReuse);
   TlsCertificateFile = Storage->ReadString(L"TlsCertificateFile", TlsCertificateFile);
 
@@ -1200,7 +1200,7 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
     WRITE_DATA(Integer, Ftps);
     WRITE_DATA(Integer, FtpListAll);
     WRITE_DATA(Integer, FtpHost);
-    WRITE_DATA(Integer, FtpDeleteFromCwd);
+    WRITE_DATA(Integer, FtpWorkFromCwd);
     WRITE_DATA(Bool, SslSessionReuse);
     WRITE_DATA(String, TlsCertificateFile);
 
@@ -4085,9 +4085,9 @@ void __fastcall TSessionData::SetFtpHost(TAutoSwitch value)
   SET_SESSION_PROPERTY(FtpHost);
 }
 //---------------------------------------------------------------------
-void __fastcall TSessionData::SetFtpDeleteFromCwd(TAutoSwitch value)
+void __fastcall TSessionData::SetFtpWorkFromCwd(TAutoSwitch value)
 {
-  SET_SESSION_PROPERTY(FtpDeleteFromCwd);
+  SET_SESSION_PROPERTY(FtpWorkFromCwd);
 }
 //---------------------------------------------------------------------
 void __fastcall TSessionData::SetSslSessionReuse(bool value)

+ 3 - 3
source/core/SessionData.h

@@ -194,7 +194,7 @@ private:
   TAutoSwitch FSCPLsFullTime;
   TAutoSwitch FFtpListAll;
   TAutoSwitch FFtpHost;
-  TAutoSwitch FFtpDeleteFromCwd;
+  TAutoSwitch FFtpWorkFromCwd;
   bool FSslSessionReuse;
   UnicodeString FTlsCertificateFile;
   TAddressFamily FAddressFamily;
@@ -369,7 +369,7 @@ private:
   void __fastcall SetSCPLsFullTime(TAutoSwitch value);
   void __fastcall SetFtpListAll(TAutoSwitch value);
   void __fastcall SetFtpHost(TAutoSwitch value);
-  void __fastcall SetFtpDeleteFromCwd(TAutoSwitch value);
+  void __fastcall SetFtpWorkFromCwd(TAutoSwitch value);
   void __fastcall SetSslSessionReuse(bool value);
   void __fastcall SetTlsCertificateFile(UnicodeString value);
   UnicodeString __fastcall GetStorageKey();
@@ -640,7 +640,7 @@ public:
   __property TAutoSwitch SCPLsFullTime = { read = FSCPLsFullTime, write = SetSCPLsFullTime };
   __property TAutoSwitch FtpListAll = { read = FFtpListAll, write = SetFtpListAll };
   __property TAutoSwitch FtpHost = { read = FFtpHost, write = SetFtpHost };
-  __property TAutoSwitch FtpDeleteFromCwd = { read = FFtpDeleteFromCwd, write = SetFtpDeleteFromCwd };
+  __property TAutoSwitch FtpWorkFromCwd = { read = FFtpWorkFromCwd, write = SetFtpWorkFromCwd };
   __property bool SslSessionReuse = { read = FSslSessionReuse, write = SetSslSessionReuse };
   __property UnicodeString TlsCertificateFile = { read=FTlsCertificateFile, write=SetTlsCertificateFile };
   __property TDSTMode DSTMode = { read = FDSTMode, write = SetDSTMode };

+ 4 - 0
source/core/SessionInfo.cpp

@@ -1362,6 +1362,10 @@ void __fastcall TSessionLog::DoAddStartupInfo(TSessionData * Data)
          EnumName(Data->FtpUseMlsd, AutoSwitchNames),
          EnumName(Data->FtpListAll, AutoSwitchNames),
          EnumName(Data->FtpHost, AutoSwitchNames)));
+      if (Data->FtpWorkFromCwd != asAuto)
+      {
+        ADF(L"FTP: Relative paths: %s", (EnumName(Data->FtpWorkFromCwd, AutoSwitchNames)));
+      }
     }
     if (Data->FSProtocol == fsWebDAV)
     {