Martin Prikryl 10 years ago
parent
commit
87c5906b1d
100 changed files with 2020 additions and 702 deletions
  1. 38 31
      deployment/winscpsetup.iss
  2. 1 0
      dotnet/GlobalSuppressions.cs
  3. 3 0
      dotnet/RemoteFileInfo.cs
  4. 63 4
      dotnet/Session.cs
  5. 209 5
      dotnet/SessionOptions.cs
  6. 3 3
      dotnet/properties/AssemblyInfo.cs
  7. 1 1
      source/Console.cbproj
  8. 1 1
      source/DragExt.cbproj
  9. 3 3
      source/DragExt64.rc
  10. 0 3
      source/FileZilla.cbproj
  11. 0 1
      source/ScpCore.cbproj
  12. 2 2
      source/WinSCP.cbproj
  13. 2 2
      source/components/UnixDirView.cpp
  14. 2 7
      source/core/Bookmarks.cpp
  15. 43 6
      source/core/Common.cpp
  16. 5 2
      source/core/Common.h
  17. 10 0
      source/core/Configuration.cpp
  18. 8 0
      source/core/Configuration.h
  19. 31 10
      source/core/FileMasks.cpp
  20. 6 4
      source/core/FileMasks.h
  21. 545 74
      source/core/FtpFileSystem.cpp
  22. 21 4
      source/core/FtpFileSystem.h
  23. 1 3
      source/core/HierarchicalStorage.cpp
  24. 2 0
      source/core/PuttyIntf.cpp
  25. 9 4
      source/core/RemoteFiles.cpp
  26. 2 1
      source/core/RemoteFiles.h
  27. 39 1
      source/core/Script.cpp
  28. 1 0
      source/core/Script.h
  29. 39 8
      source/core/SecureShell.cpp
  30. 1 1
      source/core/SecureShell.h
  31. 135 33
      source/core/SessionData.cpp
  32. 18 4
      source/core/SessionData.h
  33. 48 3
      source/core/SessionInfo.cpp
  34. 20 2
      source/core/SessionInfo.h
  35. 69 36
      source/core/SftpFileSystem.cpp
  36. 5 1
      source/core/SftpFileSystem.h
  37. 54 11
      source/core/Terminal.cpp
  38. 14 0
      source/core/Terminal.h
  39. 40 18
      source/core/WebDAVFileSystem.cpp
  40. 3 0
      source/filezilla/AsyncSocketEx.cpp
  41. 1 0
      source/filezilla/AsyncSocketEx.h
  42. 5 2
      source/filezilla/FileZillaIntf.cpp
  43. 2 0
      source/filezilla/FileZillaOpt.h
  44. 80 26
      source/filezilla/FtpControlSocket.cpp
  45. 2 0
      source/filezilla/FtpControlSocket.h
  46. 1 5
      source/filezilla/FtpListResult.cpp
  47. 2 19
      source/filezilla/MFC64bitFix.cpp
  48. 0 3
      source/filezilla/MFC64bitFix.h
  49. 0 98
      source/filezilla/PathFunctions.cpp
  50. 0 28
      source/filezilla/PathFunctions.h
  51. 19 1
      source/filezilla/TransferSocket.cpp
  52. 1 0
      source/filezilla/TransferSocket.h
  53. 2 0
      source/forms/Cleanup.cpp
  54. 0 1
      source/forms/Cleanup.dfm
  55. 21 1
      source/forms/Copy.cpp
  56. 25 2
      source/forms/Copy.dfm
  57. 3 0
      source/forms/Copy.h
  58. 1 1
      source/forms/CustomCommand.cpp
  59. 64 55
      source/forms/CustomScpExplorer.cpp
  60. 4 3
      source/forms/CustomScpExplorer.h
  61. 12 3
      source/forms/FileSystemInfo.cpp
  62. 7 6
      source/forms/FileSystemInfo.dfm
  63. 2 0
      source/forms/FileSystemInfo.h
  64. 6 34
      source/forms/GenerateUrl.cpp
  65. 1 1
      source/forms/ImportSessions.cpp
  66. 2 8
      source/forms/LocationProfiles.cpp
  67. 12 25
      source/forms/Login.cpp
  68. 1 1
      source/forms/Login.h
  69. 1 1
      source/forms/MessageDlg.cpp
  70. 8 8
      source/forms/NonVisual.cpp
  71. 10 0
      source/forms/NonVisual.dfm
  72. 2 0
      source/forms/NonVisual.h
  73. 8 3
      source/forms/Preferences.cpp
  74. 1 5
      source/forms/Preferences.dfm
  75. 43 9
      source/forms/Properties.cpp
  76. 5 7
      source/forms/Properties.dfm
  77. 2 0
      source/forms/Properties.h
  78. 2 0
      source/forms/Rights.cpp
  79. 1 0
      source/forms/Rights.h
  80. 8 3
      source/forms/RightsExt.cpp
  81. 2 0
      source/forms/RightsExt.h
  82. 8 8
      source/forms/ScpCommander.cpp
  83. 4 0
      source/forms/ScpCommander.dfm
  84. 2 1
      source/forms/ScpCommander.h
  85. 4 2
      source/forms/ScpExplorer.cpp
  86. 4 0
      source/forms/ScpExplorer.dfm
  87. 2 1
      source/forms/ScpExplorer.h
  88. 3 0
      source/forms/SiteAdvanced.cpp
  89. 20 2
      source/forms/SiteAdvanced.dfm
  90. 2 0
      source/forms/SiteAdvanced.h
  91. 27 2
      source/forms/Synchronize.cpp
  92. 47 33
      source/forms/Synchronize.dfm
  93. 8 0
      source/forms/Synchronize.h
  94. 8 1
      source/packages/filemng/CustomDirView.pas
  95. 4 1
      source/resource/TextsCore.h
  96. 3 1
      source/resource/TextsCore1.rc
  97. 5 0
      source/resource/TextsCore2.rc
  98. 1 1
      source/resource/TextsWin.h
  99. 1 1
      source/resource/TextsWin1.rc
  100. 1 0
      source/windows/ConsoleRunner.cpp

+ 38 - 31
deployment/winscpsetup.iss

@@ -1178,38 +1178,13 @@ begin
     // installation. double check that ssPostInstall was called
     // installation. double check that ssPostInstall was called
     if InstallationDone then
     if InstallationDone then
     begin
     begin
-      CanPostInstallRuns := (not WizardSilent) and (not WillRestart);
-      if CanPostInstallRuns then
-      begin
-        OpenGettingStarted :=
-          OpenGettingStartedCheckbox.Enabled and
-           OpenGettingStartedCheckbox.Checked;
-
-        if OpenGettingStarted then
-        begin
-          WebGettingStarted :=
-            ExpandConstant('{#WebGettingStarted}') + PrevVersion;
-          Log('Opening getting started page: ' + WebGettingStarted);
-          OpenBrowser(WebGettingStarted);
-        end;
+      Path := ExpandConstant('{app}\{#MainFileName}');
 
 
-        if LaunchCheckbox.Checked then
-        begin
-          if OpenGettingStarted then
-          begin
-            Log('Will launch WinSCP minimized');
-            ShowCmd := SW_SHOWMINIMIZED
-          end
-            else
-          begin
-            ShowCmd := SW_SHOWNORMAL;
-          end;
+      CanPostInstallRuns := (not WizardSilent) and (not WillRestart);
 
 
-          Log('Launching WinSCP');
-          Path := ExpandConstant('{app}\{#MainFileName}');
-          ExecAsOriginalUser(Path, '', '', ShowCmd, ewNoWait, ErrorCode)
-        end;
-      end;
+      OpenGettingStarted :=
+        OpenGettingStartedCheckbox.Enabled and
+         OpenGettingStartedCheckbox.Checked;
 
 
       UsageData := '/Usage=';
       UsageData := '/Usage=';
       
       
@@ -1253,8 +1228,40 @@ begin
       if Donated then
       if Donated then
         UsageData := UsageData + 'InstallationsDonate+,';
         UsageData := UsageData + 'InstallationsDonate+,';
 
 
+      // have to do this before running WinSCP GUI instance below,
+      // otherwise it loads the empty/previous counters and overwrites our changes,
+      // when it's closed
       Log('Recording installer usage statistics: ' + UsageData);
       Log('Recording installer usage statistics: ' + UsageData);
-      Exec(ExpandConstant('{app}\WinSCP.exe'), UsageData, '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
+      // make sure we write the counters using the "normal" account
+      // (the account that will be used to report the counters)
+      ExecAsOriginalUser(Path, UsageData, '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
+
+      if CanPostInstallRuns then
+      begin
+        if OpenGettingStarted then
+        begin
+          WebGettingStarted :=
+            ExpandConstant('{#WebGettingStarted}') + PrevVersion;
+          Log('Opening getting started page: ' + WebGettingStarted);
+          OpenBrowser(WebGettingStarted);
+        end;
+
+        if LaunchCheckbox.Checked then
+        begin
+          if OpenGettingStarted then
+          begin
+            Log('Will launch WinSCP minimized');
+            ShowCmd := SW_SHOWMINIMIZED
+          end
+            else
+          begin
+            ShowCmd := SW_SHOWNORMAL;
+          end;
+
+          Log('Launching WinSCP');
+          ExecAsOriginalUser(Path, '', '', ShowCmd, ewNoWait, ErrorCode)
+        end;
+      end;
     end;
     end;
   end;
   end;
 end;
 end;

+ 1 - 0
dotnet/GlobalSuppressions.cs

@@ -150,3 +150,4 @@ using System.Diagnostics.CodeAnalysis;
 [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess.#TryCreateEvent(System.String,System.Threading.EventWaitHandle&)")]
 [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess.#TryCreateEvent(System.String,System.Threading.EventWaitHandle&)")]
 [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Scope = "member", Target = "WinSCP.ExeSessionProcess.#TryCreateEvent(System.String,System.Threading.EventWaitHandle&)")]
 [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Scope = "member", Target = "WinSCP.ExeSessionProcess.#TryCreateEvent(System.String,System.Threading.EventWaitHandle&)")]
 [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.SessionOptions.#GetPassword()")]
 [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.SessionOptions.#GetPassword()")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Scope = "member", Target = "WinSCP.SessionOptions.#ParseUrl(System.String)")]

+ 3 - 0
dotnet/RemoteFileInfo.cs

@@ -17,6 +17,9 @@ namespace WinSCP
         public DateTime LastWriteTime { get; internal set; }
         public DateTime LastWriteTime { get; internal set; }
         public FilePermissions FilePermissions { get; internal set; }
         public FilePermissions FilePermissions { get; internal set; }
 
 
+        public string Owner { get; internal set; }
+        public string Group { get; internal set; }
+
         public bool IsDirectory { get { return (Char.ToUpper(FileType, CultureInfo.InvariantCulture) == 'D'); } }
         public bool IsDirectory { get { return (Char.ToUpper(FileType, CultureInfo.InvariantCulture) == 'D'); } }
 
 
         internal RemoteFileInfo()
         internal RemoteFileInfo()

+ 63 - 4
dotnet/Session.cs

@@ -56,6 +56,7 @@ namespace WinSCP
         public bool GuardProcessWithJob { get { return GuardProcessWithJobInternal; } set { GuardProcessWithJobInternal = value; } }
         public bool GuardProcessWithJob { get { return GuardProcessWithJobInternal; } set { GuardProcessWithJobInternal = value; } }
         public bool TestHandlesClosed { get { return TestHandlesClosedInternal; } set { TestHandlesClosedInternal = value; } }
         public bool TestHandlesClosed { get { return TestHandlesClosedInternal; } set { TestHandlesClosedInternal = value; } }
         #endif
         #endif
+        public string HomePath { get { CheckOpened(); return _homePath; } }
 
 
         public TimeSpan Timeout { get; set; }
         public TimeSpan Timeout { get; set; }
 
 
@@ -234,6 +235,24 @@ namespace WinSCP
                     {
                     {
                         ReadElement(groupReader, LogReadFlags.ThrowFailures);
                         ReadElement(groupReader, LogReadFlags.ThrowFailures);
                     }
                     }
+
+                    WriteCommand("pwd");
+
+                    using (ElementLogReader groupReader = _reader.WaitForGroupAndCreateLogReader())
+                    using (ElementLogReader cwdReader = groupReader.WaitForNonEmptyElementAndCreateLogReader("cwd", LogReadFlags.ThrowFailures))
+                    {
+                        while (cwdReader.Read(0))
+                        {
+                            string value;
+                            if (cwdReader.GetEmptyElementValue("cwd", out value))
+                            {
+                                _homePath = value;
+                            }
+                        }
+
+                        groupReader.ReadToEnd(LogReadFlags.ThrowFailures);
+                    }
+
                 }
                 }
                 catch(Exception e)
                 catch(Exception e)
                 {
                 {
@@ -661,6 +680,33 @@ namespace WinSCP
             }
             }
         }
         }
 
 
+        public string CalculateFileChecksum(string algorithm, string path)
+        {
+            using (Logger.CreateCallstack())
+            {
+                WriteCommand(string.Format(CultureInfo.InvariantCulture, "checksum -- \"{0}\" \"{1}\"", Tools.ArgumentEscape(algorithm), Tools.ArgumentEscape(path)));
+
+                string result = null;
+
+                using (ElementLogReader groupReader = _reader.WaitForGroupAndCreateLogReader())
+                using (ElementLogReader checksumReader = groupReader.WaitForNonEmptyElementAndCreateLogReader("checksum", LogReadFlags.ThrowFailures))
+                {
+                    while (checksumReader.Read(0))
+                    {
+                        string value;
+                        if (checksumReader.GetEmptyElementValue("checksum", out value))
+                        {
+                            result = value;
+                        }
+                    }
+
+                    groupReader.ReadToEnd(LogReadFlags.ThrowFailures);
+                }
+
+                return result;
+            }
+        }
+
         public void CreateDirectory(string path)
         public void CreateDirectory(string path)
         {
         {
             using (Logger.CreateCallstackAndLock())
             using (Logger.CreateCallstackAndLock())
@@ -756,6 +802,14 @@ namespace WinSCP
                 {
                 {
                     fileInfo.FilePermissions = FilePermissions.CreateReadOnlyFromText(value);
                     fileInfo.FilePermissions = FilePermissions.CreateReadOnlyFromText(value);
                 }
                 }
+                else if (fileReader.GetEmptyElementValue("owner", out value))
+                {
+                    fileInfo.Owner = value;
+                }
+                else if (fileReader.GetEmptyElementValue("group", out value))
+                {
+                    fileInfo.Group = value;
+                }
             }
             }
         }
         }
 
 
@@ -952,6 +1006,8 @@ namespace WinSCP
                     throw new ArgumentException("SessionOptions.HostName is not set.");
                     throw new ArgumentException("SessionOptions.HostName is not set.");
                 }
                 }
 
 
+                // We should wrap IPv6 literals to square brackets, instead of URL-encoding them,
+                // but it works anyway...
                 tail += UriEscape(sessionOptions.HostName);
                 tail += UriEscape(sessionOptions.HostName);
 
 
                 if (sessionOptions.PortNumber != 0)
                 if (sessionOptions.PortNumber != 0)
@@ -1047,12 +1103,14 @@ namespace WinSCP
                             switches.Add(FormatSwitch("implicit"));
                             switches.Add(FormatSwitch("implicit"));
                             break;
                             break;
 
 
-                        case FtpSecure.ExplicitSsl:
-                            switches.Add(FormatSwitch("explicitssl"));
+                        case FtpSecure.Explicit: // and ExplicitTls
+                            switches.Add(FormatSwitch("explicit"));
                             break;
                             break;
 
 
-                        case FtpSecure.ExplicitTls:
-                            switches.Add(FormatSwitch("explicittls"));
+#pragma warning disable 618
+                        case FtpSecure.ExplicitSsl:
+#pragma warning restore 618
+                            switches.Add(FormatSwitch("explicitssl"));
                             break;
                             break;
 
 
                         default:
                         default:
@@ -1643,5 +1701,6 @@ namespace WinSCP
         private FileTransferProgressEventHandler _fileTransferProgress;
         private FileTransferProgressEventHandler _fileTransferProgress;
         private int _progressHandling;
         private int _progressHandling;
         private bool _guardProcessWithJob;
         private bool _guardProcessWithJob;
+        private string _homePath;
     }
     }
 }
 }

+ 209 - 5
dotnet/SessionOptions.cs

@@ -31,7 +31,10 @@ namespace WinSCP
     {
     {
         None = 0,
         None = 0,
         Implicit = 1,
         Implicit = 1,
-        ExplicitTls = 3,
+        Explicit = 3,
+        [Obsolete("Use FtpSecure.Explicit")]
+        ExplicitTls = Explicit,
+        [Obsolete("Use FtpSecure.Explicit")]
         ExplicitSsl = 2,
         ExplicitSsl = 2,
     }
     }
 
 
@@ -78,6 +81,200 @@ namespace WinSCP
             RawSettings.Add(setting, value);
             RawSettings.Add(setting, value);
         }
         }
 
 
+        public void ParseUrl(string url)
+        {
+            url = url.Trim();
+            const string protocolSeparator = "://";
+            int index = url.IndexOf(protocolSeparator, StringComparison.OrdinalIgnoreCase);
+            if (index < 0)
+            {
+                throw new ArgumentException("Protocol not specified", "url");
+            }
+            string protocol = url.Substring(0, index).Trim();
+
+            FtpSecure = FtpSecure.None;
+
+            if (protocol.Equals("sftp", StringComparison.OrdinalIgnoreCase))
+            {
+                Protocol = Protocol.Sftp;
+            }
+            else if (protocol.Equals("scp", StringComparison.OrdinalIgnoreCase))
+            {
+                Protocol = Protocol.Scp;
+            }
+            else if (protocol.Equals("ftp", StringComparison.OrdinalIgnoreCase))
+            {
+                Protocol = Protocol.Ftp;
+            }
+            else if (protocol.Equals("ftps", StringComparison.OrdinalIgnoreCase))
+            {
+                Protocol = Protocol.Ftp;
+                FtpSecure = FtpSecure.Implicit;
+            }
+            else if (protocol.Equals("http", StringComparison.OrdinalIgnoreCase))
+            {
+                Protocol = Protocol.Webdav;
+            }
+            else if (protocol.Equals("https", StringComparison.OrdinalIgnoreCase))
+            {
+                Protocol = Protocol.Webdav;
+                WebdavSecure = true;
+            }
+            else
+            {
+                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Unknown protocol {0}", protocol), "url");
+            }
+
+            url = url.Substring(index + protocolSeparator.Length).Trim();
+            index = url.IndexOf('/');
+            WebdavRoot = null;
+            if (index >= 0)
+            {
+                string path = url.Substring(index).Trim();
+                url = url.Substring(0, index).Trim();
+                string parameters = path;
+                path = CutToChar(ref parameters, ';');
+                if (!string.IsNullOrEmpty(path) && (path != "/"))
+                {
+                    if (Protocol != Protocol.Webdav)
+                    {
+                        throw new ArgumentException("Root folder can be specified for WebDAV protocol only", "url");
+                    }
+                    WebdavRoot = path;
+                }
+
+                // forward compatibility
+                if (!string.IsNullOrEmpty(parameters))
+                {
+                    throw new ArgumentException("No session parameters are supported", "url");
+                }
+            }
+
+            index = url.LastIndexOf('@');
+
+            string hostInfo;
+            string userInfo = null;
+            if (index >= 0)
+            {
+                userInfo = url.Substring(0, index).Trim();
+                hostInfo = url.Substring(index + 1).Trim();
+            }
+            else
+            {
+                hostInfo = url;
+            }
+
+            PortNumber = 0;
+            string portNumber = null;
+            if ((hostInfo.Length >= 2) && (hostInfo[0] == '[') && ((index = hostInfo.IndexOf(']')) > 0))
+            {
+                HostName = hostInfo.Substring(1, index - 1).Trim();
+                hostInfo = hostInfo.Substring(index + 1).Trim();
+                if (hostInfo.Length > 0)
+                {
+                    if (hostInfo[0] != ':')
+                    {
+                        throw new ArgumentException("Unexpected syntax after ]", "url");
+                    }
+                    else
+                    {
+                        portNumber = hostInfo.Substring(1);
+                    }
+                }
+            }
+            else
+            {
+                HostName = UriUnescape(CutToChar(ref hostInfo, ':'));
+                portNumber = hostInfo;
+            }
+
+            if (string.IsNullOrEmpty(HostName))
+            {
+                throw new ArgumentException("No host name", "url");
+            }
+
+            if (string.IsNullOrEmpty(portNumber))
+            {
+                PortNumber = 0;
+            }
+            else
+            {
+                portNumber = UriUnescape(portNumber);
+                int number;
+                if (!int.TryParse(portNumber, 0, CultureInfo.InvariantCulture, out number))
+                {
+                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} is not a valid port number", portNumber), "url");
+                }
+                else
+                {
+                    PortNumber = number;
+                }
+            }
+
+            UserName = null;
+            Password = null;
+            SshHostKeyFingerprint = null;
+            GiveUpSecurityAndAcceptAnySshHostKey = false;
+            TlsHostCertificateFingerprint = null;
+            GiveUpSecurityAndAcceptAnyTlsHostCertificate = false;
+            if (!string.IsNullOrEmpty(userInfo))
+            {
+                string parameters = userInfo;
+                userInfo = CutToChar(ref parameters, ';');
+
+                UserName = EmptyToNull(UriUnescape(CutToChar(ref userInfo, ':')));
+                Password = EmptyToNull(UriUnescape(userInfo));
+
+                while (!string.IsNullOrEmpty(parameters))
+                {
+                    string parameter = CutToChar(ref parameters, ';');
+                    string parameterName = CutToChar(ref parameter, '=');
+                    if (parameterName.Equals("fingerprint", StringComparison.OrdinalIgnoreCase))
+                    {
+                        SshHostKeyFingerprint = parameter;
+                    }
+                    else
+                    {
+                        throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Unsupported connection parameter {0}", parameterName), "url");
+                    }
+                }
+            }
+        }
+
+        private static string EmptyToNull(string s)
+        {
+            if (string.IsNullOrEmpty(s))
+            {
+                return null;
+            }
+            else
+            {
+                return s;
+            }
+        }
+
+        private static string UriUnescape(string s)
+        {
+            return Uri.UnescapeDataString(s);
+        }
+
+        private static string CutToChar(ref string s, char c)
+        {
+            int index = s.IndexOf(c);
+            string result;
+            if (index >= 0)
+            {
+                result = s.Substring(0, index).Trim();
+                s = s.Substring(index + 1).Trim();
+            }
+            else
+            {
+                result = s;
+                s = string.Empty;
+            }
+            return result;
+        }
+
         internal Dictionary<string, string> RawSettings { get; private set; }
         internal Dictionary<string, string> RawSettings { get; private set; }
         internal bool IsSsh { get { return (Protocol == Protocol.Sftp) || (Protocol == Protocol.Scp); } }
         internal bool IsSsh { get { return (Protocol == Protocol.Sftp) || (Protocol == Protocol.Scp); } }
 
 
@@ -142,10 +339,17 @@ namespace WinSCP
 
 
         private void SetPassword(string value)
         private void SetPassword(string value)
         {
         {
-            SecurePassword = new SecureString();
-            foreach (char c in value)
+            if (value == null)
             {
             {
-                SecurePassword.AppendChar(c);
+                SecurePassword = null;
+            }
+            else
+            {
+                SecurePassword = new SecureString();
+                foreach (char c in value)
+                {
+                    SecurePassword.AppendChar(c);
+                }
             }
             }
         }
         }
 
 
@@ -177,7 +381,7 @@ namespace WinSCP
         private string _webdavRoot;
         private string _webdavRoot;
 
 
         private const string _listPattern = @"{0}(;{0})*";
         private const string _listPattern = @"{0}(;{0})*";
-        private const string _sshHostKeyPattern = @"(ssh-rsa |ssh-dss )?\d+ ([0-9a-f]{2}:){15}[0-9a-f]{2}";
+        private const string _sshHostKeyPattern = @"((ssh-rsa|ssh-dss)( |-))?(\d+ )?([0-9a-f]{2}(:|-)){15}[0-9a-f]{2}";
         private static readonly Regex _sshHostKeyRegex =
         private static readonly Regex _sshHostKeyRegex =
             new Regex(string.Format(CultureInfo.InvariantCulture, _listPattern, _sshHostKeyPattern));
             new Regex(string.Format(CultureInfo.InvariantCulture, _listPattern, _sshHostKeyPattern));
         private const string _tlsCertificatePattern = @"([0-9a-f]{2}:){19}[0-9a-f]{2}";
         private const string _tlsCertificatePattern = @"([0-9a-f]{2}:){19}[0-9a-f]{2}";

+ 3 - 3
dotnet/properties/AssemblyInfo.cs

@@ -19,9 +19,9 @@ using System.Runtime.InteropServices;
 // The following GUID is for the ID of the typelib if this project is exposed to COM
 // The following GUID is for the ID of the typelib if this project is exposed to COM
 [assembly: Guid("a0b93468-d98a-4845-a234-8076229ad93f")]
 [assembly: Guid("a0b93468-d98a-4845-a234-8076229ad93f")]
 
 
-[assembly: AssemblyVersion("1.2.3.0")]
-[assembly: AssemblyFileVersion("1.2.3.0")]
-[assembly: AssemblyInformationalVersionAttribute("5.6.3.0")]
+[assembly: AssemblyVersion("1.2.4.0")]
+[assembly: AssemblyFileVersion("1.2.4.0")]
+[assembly: AssemblyInformationalVersionAttribute("5.6.4.0")]
 
 
 [assembly: CLSCompliant(true)]
 [assembly: CLSCompliant(true)]
 
 

+ 1 - 1
source/Console.cbproj

@@ -65,7 +65,7 @@
 			<ProjectType>CppConsoleApplication</ProjectType>
 			<ProjectType>CppConsoleApplication</ProjectType>
 			<SanitizedProjectName>Console</SanitizedProjectName>
 			<SanitizedProjectName>Console</SanitizedProjectName>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=4.1.0.0;InternalName=console;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.6.3.0;ReleaseType=beta;WWW=http://winscp.net/</VerInfo_Keys>
+			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=4.1.0.0;InternalName=console;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.6.4.0;ReleaseType=RC;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MajorVer>4</VerInfo_MajorVer>
 			<VerInfo_MajorVer>4</VerInfo_MajorVer>
 			<VerInfo_MinorVer>1</VerInfo_MinorVer>
 			<VerInfo_MinorVer>1</VerInfo_MinorVer>

+ 1 - 1
source/DragExt.cbproj

@@ -66,7 +66,7 @@
 			<SanitizedProjectName>DragExt</SanitizedProjectName>
 			<SanitizedProjectName>DragExt</SanitizedProjectName>
 			<VerInfo_DLL>true</VerInfo_DLL>
 			<VerInfo_DLL>true</VerInfo_DLL>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Drag&amp;Drop shell extension for WinSCP (32-bit);FileVersion=1.2.1.0;InternalName=dragext32;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=dragext.dll;ProductName=WinSCP;ProductVersion=5.6.3.0;ReleaseType=beta;WWW=http://winscp.net/</VerInfo_Keys>
+			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Drag&amp;Drop shell extension for WinSCP (32-bit);FileVersion=1.2.1.0;InternalName=dragext32;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=dragext.dll;ProductName=WinSCP;ProductVersion=5.6.4.0;ReleaseType=RC;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MinorVer>2</VerInfo_MinorVer>
 			<VerInfo_MinorVer>2</VerInfo_MinorVer>
 			<VerInfo_Release>1</VerInfo_Release>
 			<VerInfo_Release>1</VerInfo_Release>

+ 3 - 3
source/DragExt64.rc

@@ -1,6 +1,6 @@
 1 VERSIONINFO
 1 VERSIONINFO
 FILEVERSION 1,2,1,0
 FILEVERSION 1,2,1,0
-PRODUCTVERSION 5,6,3,0
+PRODUCTVERSION 5,6,4,0
 FILEOS 0x4
 FILEOS 0x4
 FILETYPE 0x2
 FILETYPE 0x2
 {
 {
@@ -16,8 +16,8 @@ FILETYPE 0x2
             VALUE "LegalTrademarks", "\0"
             VALUE "LegalTrademarks", "\0"
             VALUE "OriginalFilename", "dragext64.dll\0"
             VALUE "OriginalFilename", "dragext64.dll\0"
             VALUE "ProductName", "WinSCP\0"
             VALUE "ProductName", "WinSCP\0"
-            VALUE "ProductVersion", "5.6.3.0\0"
-            VALUE "ReleaseType", "beta\0"
+            VALUE "ProductVersion", "5.6.4.0\0"
+            VALUE "ReleaseType", "RC\0"
             VALUE "WWW", "http://winscp.net/\0"
             VALUE "WWW", "http://winscp.net/\0"
         }
         }
     }
     }

+ 0 - 3
source/FileZilla.cbproj

@@ -186,9 +186,6 @@
 			<CppCompile Include="filezilla\Options.cpp">
 			<CppCompile Include="filezilla\Options.cpp">
 			<DependentOn>filezilla\Options.h</DependentOn>
 			<DependentOn>filezilla\Options.h</DependentOn>
 			<BuildOrder>15</BuildOrder>
 			<BuildOrder>15</BuildOrder>
-		</CppCompile>
-			<CppCompile Include="filezilla\PathFunctions.cpp">
-			<BuildOrder>16</BuildOrder>
 		</CppCompile>
 		</CppCompile>
 			<CppCompile Include="filezilla\ServerPath.cpp">
 			<CppCompile Include="filezilla\ServerPath.cpp">
 			<BuildOrder>17</BuildOrder>
 			<BuildOrder>17</BuildOrder>

+ 0 - 1
source/ScpCore.cbproj

@@ -234,7 +234,6 @@
 			<BuildOrder>83</BuildOrder>
 			<BuildOrder>83</BuildOrder>
 		</CppCompile>
 		</CppCompile>
 			<CppCompile Include="core\Usage.cpp">
 			<CppCompile Include="core\Usage.cpp">
-			<DependentOn>core\Usage.h</DependentOn>
 			<BuildOrder>27</BuildOrder>
 			<BuildOrder>27</BuildOrder>
 		</CppCompile>
 		</CppCompile>
 			<CppCompile Include="core\WebDAVFileSystem.cpp">
 			<CppCompile Include="core\WebDAVFileSystem.cpp">

+ 2 - 2
source/WinSCP.cbproj

@@ -83,11 +83,11 @@
 			<SanitizedProjectName>WinSCP</SanitizedProjectName>
 			<SanitizedProjectName>WinSCP</SanitizedProjectName>
 			<UsingDelphiRTL>true</UsingDelphiRTL>
 			<UsingDelphiRTL>true</UsingDelphiRTL>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
 			<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
-			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=WinSCP: SFTP, FTP and SCP client;FileVersion=5.6.3.0;InternalName=winscp;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.exe;ProductName=WinSCP;ProductVersion=5.6.3.0;ReleaseType=beta;WWW=http://winscp.net/</VerInfo_Keys>
+			<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=WinSCP: SFTP, FTP and SCP client;FileVersion=5.6.4.0;InternalName=winscp;LegalCopyright=(c) 2000-2014 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.exe;ProductName=WinSCP;ProductVersion=5.6.4.0;ReleaseType=RC;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MajorVer>5</VerInfo_MajorVer>
 			<VerInfo_MajorVer>5</VerInfo_MajorVer>
 			<VerInfo_MinorVer>6</VerInfo_MinorVer>
 			<VerInfo_MinorVer>6</VerInfo_MinorVer>
-			<VerInfo_Release>3</VerInfo_Release>
+			<VerInfo_Release>4</VerInfo_Release>
 		</PropertyGroup>
 		</PropertyGroup>
 	<PropertyGroup Condition="'$(Base_Win32)'!=''">
 	<PropertyGroup Condition="'$(Base_Win32)'!=''">
 			<Defines>IDE;STRICT;$(Defines)</Defines>
 			<Defines>IDE;STRICT;$(Defines)</Defines>

+ 2 - 2
source/components/UnixDirView.cpp

@@ -289,7 +289,7 @@ __int64 __fastcall TUnixDirView::ItemFileSize(TListItem * Item)
 {
 {
 #ifndef DESIGN_ONLY
 #ifndef DESIGN_ONLY
   ASSERT_VALID_ITEM;
   ASSERT_VALID_ITEM;
-  return ITEMFILE->IsDirectory ? 0 : ITEMFILE->Size;
+  return ITEMFILE->Size;
 #else
 #else
   USEDPARAM(Item);
   USEDPARAM(Item);
   return 0;
   return 0;
@@ -401,7 +401,7 @@ void __fastcall TUnixDirView::LoadFiles()
       {
       {
         VisibleFiles++;
         VisibleFiles++;
 
 
-        if (!File->IsDirectory) FFilesSize += File->Size;
+        FFilesSize += File->Size;
         if (File->IsParentDirectory) FHasParentDir = true;
         if (File->IsParentDirectory) FHasParentDir = true;
 
 
         TListItem * Item = new TListItem(Items);
         TListItem * Item = new TListItem(Items);

+ 2 - 7
source/core/Bookmarks.cpp

@@ -13,10 +13,7 @@
 __fastcall TBookmarks::TBookmarks(): TObject()
 __fastcall TBookmarks::TBookmarks(): TObject()
 {
 {
   FSharedKey = TNamedObjectList::HiddenPrefix + L"shared";
   FSharedKey = TNamedObjectList::HiddenPrefix + L"shared";
-  FBookmarkLists = new TStringList();
-  FBookmarkLists->Sorted = true;
-  FBookmarkLists->CaseSensitive = false;
-  FBookmarkLists->Duplicates = Types::dupError;
+  FBookmarkLists = CreateSortedStringList(false, Types::dupError);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TBookmarks::~TBookmarks()
 __fastcall TBookmarks::~TBookmarks()
@@ -285,9 +282,7 @@ __fastcall TBookmarkList::TBookmarkList(): TPersistent()
   FModified = false;
   FModified = false;
   FBookmarks = new TStringList();
   FBookmarks = new TStringList();
   FBookmarks->CaseSensitive = false;
   FBookmarks->CaseSensitive = false;
-  FOpenedNodes = new TStringList();
-  FOpenedNodes->CaseSensitive = false;
-  FOpenedNodes->Sorted = true;
+  FOpenedNodes = CreateSortedStringList();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TBookmarkList::~TBookmarkList()
 __fastcall TBookmarkList::~TBookmarkList()

+ 43 - 6
source/core/Common.cpp

@@ -108,17 +108,17 @@ UnicodeString MakeValidFileName(UnicodeString FileName)
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 UnicodeString RootKeyToStr(HKEY RootKey)
 UnicodeString RootKeyToStr(HKEY RootKey)
 {
 {
-  if (RootKey == HKEY_USERS) return L"HKEY_USERS";
+  if (RootKey == HKEY_USERS) return L"HKU";
     else
     else
-  if (RootKey == HKEY_LOCAL_MACHINE) return L"HKEY_LOCAL_MACHINE";
+  if (RootKey == HKEY_LOCAL_MACHINE) return L"HKLM";
     else
     else
-  if (RootKey == HKEY_CURRENT_USER) return L"HKEY_CURRENT_USER";
+  if (RootKey == HKEY_CURRENT_USER) return L"HKCU";
     else
     else
-  if (RootKey == HKEY_CLASSES_ROOT) return L"HKEY_CLASSES_ROOT";
+  if (RootKey == HKEY_CLASSES_ROOT) return L"HKCR";
     else
     else
-  if (RootKey == HKEY_CURRENT_CONFIG) return L"HKEY_CURRENT_CONFIG";
+  if (RootKey == HKEY_CURRENT_CONFIG) return L"HKCC";
     else
     else
-  if (RootKey == HKEY_DYN_DATA) return L"HKEY_DYN_DATA";
+  if (RootKey == HKEY_DYN_DATA) return L"HKDD";
     else
     else
   {  Abort(); return L""; };
   {  Abort(); return L""; };
 }
 }
@@ -2465,3 +2465,40 @@ int __fastcall ParseShortEngMonthName(const UnicodeString & MonthStr)
   return IndexStr(MonthStr, FormatSettings.ShortMonthNames, FormatSettings.ShortMonthNames.Size()) + 1;
   return IndexStr(MonthStr, FormatSettings.ShortMonthNames, FormatSettings.ShortMonthNames.Size()) + 1;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+TStringList * __fastcall CreateSortedStringList(bool CaseSensitive, System::Types::TDuplicates Duplicates)
+{
+  TStringList * Result = new TStringList();
+  Result->CaseSensitive = CaseSensitive;
+  Result->Duplicates = Duplicates;
+  return Result;
+}
+//---------------------------------------------------------------------------
+static UnicodeString __fastcall NormalizeIdent(UnicodeString Ident)
+{
+  int Index = 1;
+  while (Index <= Ident.Length())
+  {
+    if (Ident[Index] == L'-')
+    {
+      Ident.Delete(Index, 1);
+    }
+    else
+    {
+      Index++;
+    }
+  }
+  return Ident;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall FindIdent(const UnicodeString & Ident, TStrings * Idents)
+{
+  UnicodeString NormalizedIdent(NormalizeIdent(Ident));
+  for (int Index = 0; Index < Idents->Count; Index++)
+  {
+    if (SameText(NormalizedIdent, NormalizeIdent(Idents->Strings[Index])))
+    {
+      return Idents->Strings[Index];
+    }
+  }
+  return Ident;
+}

+ 5 - 2
source/core/Common.h

@@ -122,6 +122,9 @@ UnicodeString __fastcall TrimVersion(UnicodeString Version);
 UnicodeString __fastcall FormatVersion(int MajovVersion, int MinorVersion, int Release);
 UnicodeString __fastcall FormatVersion(int MajovVersion, int MinorVersion, int Release);
 TFormatSettings __fastcall GetEngFormatSettings();
 TFormatSettings __fastcall GetEngFormatSettings();
 int __fastcall ParseShortEngMonthName(const UnicodeString & MonthStr);
 int __fastcall ParseShortEngMonthName(const UnicodeString & MonthStr);
+// The defaults are equal to defaults of TStringList class (except for Sorted)
+TStringList * __fastcall CreateSortedStringList(bool CaseSensitive = false, System::Types::TDuplicates Duplicates = dupIgnore);
+UnicodeString __fastcall FindIdent(const UnicodeString & Ident, TStrings * Idents);
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 typedef void __fastcall (__closure* TProcessLocalFileEvent)
 typedef void __fastcall (__closure* TProcessLocalFileEvent)
   (const UnicodeString FileName, const TSearchRec Rec, void * Param);
   (const UnicodeString FileName, const TSearchRec Rec, void * Param);
@@ -213,8 +216,8 @@ private:
 #else
 #else
 #define CHECK(p) { bool __CHECK_RESULT__ = (p); assert(__CHECK_RESULT__); }
 #define CHECK(p) { bool __CHECK_RESULT__ = (p); assert(__CHECK_RESULT__); }
 #define FAIL assert(false)
 #define FAIL assert(false)
-#define ALWAYS_TRUE(p) p
-#define ALWAYS_FALSE(p) p
+#define ALWAYS_TRUE(p) (p)
+#define ALWAYS_FALSE(p) (p)
 #define NOT_NULL(P) P
 #define NOT_NULL(P) P
 #endif
 #endif
 #define USEDPARAM(p) ((&p) == (&p))
 #define USEDPARAM(p) ((&p) == (&p))

+ 10 - 0
source/core/Configuration.cpp

@@ -18,6 +18,16 @@
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 #pragma package(smart_init)
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+// See http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml
+const UnicodeString Sha1ChecksumAlg(L"sha-1");
+const UnicodeString Sha224ChecksumAlg(L"sha-224");
+const UnicodeString Sha256ChecksumAlg(L"sha-256");
+const UnicodeString Sha384ChecksumAlg(L"sha-384");
+const UnicodeString Sha512ChecksumAlg(L"sha-512");
+const UnicodeString Md5ChecksumAlg(L"md5");
+// Not defined by IANA
+const UnicodeString Crc32ChecksumAlg(L"crc32");
+//---------------------------------------------------------------------------
 __fastcall TConfiguration::TConfiguration()
 __fastcall TConfiguration::TConfiguration()
 {
 {
   FCriticalSection = new TCriticalSection();
   FCriticalSection = new TCriticalSection();

+ 8 - 0
source/core/Configuration.h

@@ -273,4 +273,12 @@ extern const UnicodeString KittyRegistryStorageKey;
 extern const UnicodeString OriginalPuttyExecutable;
 extern const UnicodeString OriginalPuttyExecutable;
 extern const UnicodeString KittyExecutable;
 extern const UnicodeString KittyExecutable;
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+extern const UnicodeString Sha1ChecksumAlg;
+extern const UnicodeString Sha224ChecksumAlg;
+extern const UnicodeString Sha256ChecksumAlg;
+extern const UnicodeString Sha384ChecksumAlg;
+extern const UnicodeString Sha512ChecksumAlg;
+extern const UnicodeString Md5ChecksumAlg;
+extern const UnicodeString Crc32ChecksumAlg;
+//---------------------------------------------------------------------------
 #endif
 #endif

+ 31 - 10
source/core/FileMasks.cpp

@@ -1056,21 +1056,37 @@ __fastcall TCustomCommandData::TCustomCommandData()
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TCustomCommandData::TCustomCommandData(TTerminal * Terminal)
 __fastcall TCustomCommandData::TCustomCommandData(TTerminal * Terminal)
 {
 {
-  Init(Terminal->SessionData, Terminal->UserName, Terminal->Password);
+  Init(Terminal->SessionData, Terminal->UserName, Terminal->Password,
+    Terminal->GetSessionInfo().HostKeyFingerprint);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TCustomCommandData::TCustomCommandData(
 __fastcall TCustomCommandData::TCustomCommandData(
   TSessionData * SessionData, const UnicodeString & UserName, const UnicodeString & Password)
   TSessionData * SessionData, const UnicodeString & UserName, const UnicodeString & Password)
 {
 {
-  Init(SessionData, UserName, Password);
+  Init(SessionData, UserName, Password, UnicodeString());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TCustomCommandData::Init(
 void __fastcall TCustomCommandData::Init(
-  TSessionData * SessionData, const UnicodeString & AUserName, const UnicodeString & APassword)
+  TSessionData * ASessionData, const UnicodeString & AUserName,
+  const UnicodeString & APassword, const UnicodeString & AHostKey)
 {
 {
-  HostName = SessionData->HostNameExpanded;
-  UserName = AUserName;
-  Password = APassword;
+  FSessionData.reset(new TSessionData(L""));
+  FSessionData->Assign(ASessionData);
+  FSessionData->UserName = AUserName;
+  FSessionData->Password = APassword;
+  FSessionData->HostKey = AHostKey;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomCommandData::operator=(const TCustomCommandData & Data)
+{
+  assert(Data.SessionData != NULL);
+  FSessionData.reset(new TSessionData(L""));
+  FSessionData->Assign(Data.SessionData);
+}
+//---------------------------------------------------------------------------
+TSessionData * __fastcall TCustomCommandData::GetSesssionData() const
+{
+  return FSessionData.get();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -1102,6 +1118,7 @@ int __fastcall TFileCustomCommand::PatternLen(const UnicodeString & Command, int
   wchar_t PatternCmd = (Index < Command.Length()) ? Command[Index + 1] : L'\0';
   wchar_t PatternCmd = (Index < Command.Length()) ? Command[Index + 1] : L'\0';
   switch (PatternCmd)
   switch (PatternCmd)
   {
   {
+    case L'S':
     case L'@':
     case L'@':
     case L'U':
     case L'U':
     case L'P':
     case L'P':
@@ -1122,17 +1139,21 @@ bool __fastcall TFileCustomCommand::PatternReplacement(
 {
 {
   // keep consistent with TSessionLog::OpenLogFile
   // keep consistent with TSessionLog::OpenLogFile
 
 
-  if (Pattern == L"!@")
+  if (AnsiSameText(Pattern, L"!s"))
+  {
+    Replacement = FData.SessionData->GenerateSessionUrl(sufComplete);
+  }
+  else if (Pattern == L"!@")
   {
   {
-    Replacement = FData.HostName;
+    Replacement = FData.SessionData->HostNameExpanded;
   }
   }
   else if (AnsiSameText(Pattern, L"!u"))
   else if (AnsiSameText(Pattern, L"!u"))
   {
   {
-    Replacement = FData.UserName;
+    Replacement = FData.SessionData->UserName;
   }
   }
   else if (AnsiSameText(Pattern, L"!p"))
   else if (AnsiSameText(Pattern, L"!p"))
   {
   {
-    Replacement = FData.Password;
+    Replacement = FData.SessionData->Password;
   }
   }
   else if (Pattern == L"!/")
   else if (Pattern == L"!/")
   {
   {

+ 6 - 4
source/core/FileMasks.h

@@ -183,14 +183,16 @@ struct TCustomCommandData
     TSessionData * SessionData, const UnicodeString & AUserName,
     TSessionData * SessionData, const UnicodeString & AUserName,
     const UnicodeString & Password);
     const UnicodeString & Password);
 
 
-  UnicodeString HostName;
-  UnicodeString UserName;
-  UnicodeString Password;
+  __property TSessionData * SessionData = { read = GetSesssionData };
+
+  void __fastcall operator=(const TCustomCommandData & Data);
 
 
 private:
 private:
+  std::unique_ptr<TSessionData> FSessionData;
   void __fastcall Init(
   void __fastcall Init(
     TSessionData * SessionData, const UnicodeString & AUserName,
     TSessionData * SessionData, const UnicodeString & AUserName,
-    const UnicodeString & Password);
+    const UnicodeString & Password, const UnicodeString & HostKey);
+  TSessionData * __fastcall GetSesssionData() const;
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TFileCustomCommand : public TCustomCommand
 class TFileCustomCommand : public TCustomCommand

+ 545 - 74
source/core/FtpFileSystem.cpp

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

+ 21 - 4
source/core/FtpFileSystem.h

@@ -95,7 +95,8 @@ protected:
     REPLY_CONNECT =      0x01,
     REPLY_CONNECT =      0x01,
     REPLY_2XX_CODE =     0x02,
     REPLY_2XX_CODE =     0x02,
     REPLY_ALLOW_CANCEL = 0x04,
     REPLY_ALLOW_CANCEL = 0x04,
-    REPLY_3XX_CODE =     0x08
+    REPLY_3XX_CODE =     0x08,
+    REPLY_SINGLE_LINE =  0x10
   };
   };
 
 
   bool __fastcall PostMessage(unsigned int Type, WPARAM wParam, LPARAM lParam);
   bool __fastcall PostMessage(unsigned int Type, WPARAM wParam, LPARAM lParam);
@@ -107,7 +108,7 @@ protected:
   void __fastcall WaitForFatalNonCommandReply();
   void __fastcall WaitForFatalNonCommandReply();
   void __fastcall PoolForFatalNonCommandReply();
   void __fastcall PoolForFatalNonCommandReply();
   void __fastcall GotNonCommandReply(unsigned int Reply);
   void __fastcall GotNonCommandReply(unsigned int Reply);
-  void __fastcall GotReply(unsigned int Reply, unsigned int Flags = 0,
+  UnicodeString __fastcall GotReply(unsigned int Reply, unsigned int Flags = 0,
     UnicodeString Error = L"", unsigned int * Code = NULL,
     UnicodeString Error = L"", unsigned int * Code = NULL,
     TStrings ** Response = NULL);
     TStrings ** Response = NULL);
   void __fastcall ResetReply();
   void __fastcall ResetReply();
@@ -188,6 +189,17 @@ protected:
   void __fastcall AutoDetectTimeDifference(TRemoteFileList * FileList);
   void __fastcall AutoDetectTimeDifference(TRemoteFileList * FileList);
   void __fastcall ApplyTimeDifference(TRemoteFile * File);
   void __fastcall ApplyTimeDifference(TRemoteFile * File);
   bool __fastcall TimeZoneDifferenceApplicable(TModificationFmt ModificationFmt);
   bool __fastcall TimeZoneDifferenceApplicable(TModificationFmt ModificationFmt);
+  UnicodeString __fastcall DoCalculateFileChecksum(bool UsingHashCommand, const UnicodeString & Alg, TRemoteFile * File);
+  void __fastcall DoCalculateFilesChecksum(bool UsingHashCommand, const UnicodeString & Alg,
+    TStrings * FileList, TStrings * Checksums,
+    TCalculatedChecksumEvent OnCalculatedChecksum,
+    TFileOperationProgressType * OperationProgress, bool FirstLevel);
+  void __fastcall HandleFeatReply();
+  void __fastcall ResetFeatures();
+  bool __fastcall SupportsSiteCommand(const UnicodeString & Command) const;
+  bool __fastcall SupportsCommand(const UnicodeString & Command) const;
+  void __fastcall RegisterChecksumAlgCommand(const UnicodeString & Alg, const UnicodeString & Command);
+  void __fastcall SendCommand(const UnicodeString & Command);
 
 
   static bool __fastcall Unquote(UnicodeString & Str);
   static bool __fastcall Unquote(UnicodeString & Str);
   static UnicodeString __fastcall ExtractStatusMessage(UnicodeString Status);
   static UnicodeString __fastcall ExtractStatusMessage(UnicodeString Status);
@@ -249,8 +261,13 @@ private:
   TDateTime FLastDataSent;
   TDateTime FLastDataSent;
   bool FDetectTimeDifference;
   bool FDetectTimeDifference;
   __int64 FTimeDifference;
   __int64 FTimeDifference;
-  bool FSupportsSiteCopy;
-  bool FSupportsSiteSymlink;
+  std::unique_ptr<TStrings> FChecksumAlgs;
+  std::unique_ptr<TStrings> FChecksumCommands;
+  std::unique_ptr<TStrings> FSupportedCommands;
+  std::unique_ptr<TStrings> FSupportedSiteCommands;
+  std::unique_ptr<TStrings> FHashAlgs;
+  bool FSupportsAnyChecksumFeature;
+  UnicodeString FLastCommandSent;
   mutable UnicodeString FOptionScratch;
   mutable UnicodeString FOptionScratch;
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 1 - 3
source/core/HierarchicalStorage.cpp

@@ -1414,9 +1414,7 @@ void __fastcall TOptionsIniFile::ReadSection(const UnicodeString Section, TStrin
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TOptionsIniFile::ReadSections(TStrings * Strings)
 void __fastcall TOptionsIniFile::ReadSections(TStrings * Strings)
 {
 {
-  std::unique_ptr<TStringList> Sections(new TStringList());
-  Sections->CaseSensitive = false;
-  Sections->Sorted = true;
+  std::unique_ptr<TStringList> Sections(CreateSortedStringList());
 
 
   for (int Index = 0; Index < FOptions->Count; Index++)
   for (int Index = 0; Index < FOptions->Count; Index++)
   {
   {

+ 2 - 0
source/core/PuttyIntf.cpp

@@ -144,6 +144,8 @@ int get_userpass_input(prompts_t * p, unsigned char * /*in*/, int /*inlen*/)
     {
     {
       prompt_t * Prompt = p->prompts[Index];
       prompt_t * Prompt = p->prompts[Index];
       Prompts->AddObject(Prompt->prompt, (TObject *)(FLAGMASK(Prompt->echo, pupEcho)));
       Prompts->AddObject(Prompt->prompt, (TObject *)(FLAGMASK(Prompt->echo, pupEcho)));
+      // this fails, when new passwords do not match on change password prompt,
+      // and putty retries the prompt
       assert(Prompt->resultsize == 0);
       assert(Prompt->resultsize == 0);
       Results->Add(L"");
       Results->Add(L"");
     }
     }

+ 9 - 4
source/core/RemoteFiles.cpp

@@ -842,6 +842,11 @@ void __fastcall TRemoteFile::LoadTypeInfo()
   FIconIndex = FakeFileImageIndex(DumbFileName, Attrs, &FTypeName);
   FIconIndex = FakeFileImageIndex(DumbFileName, Attrs, &FTypeName);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+__int64 __fastcall TRemoteFile::GetSize() const
+{
+  return IsDirectory ? 0 : FSize;
+}
+//---------------------------------------------------------------------------
 Integer __fastcall TRemoteFile::GetIconIndex() const
 Integer __fastcall TRemoteFile::GetIconIndex() const
 {
 {
   if (FIconIndex == -1)
   if (FIconIndex == -1)
@@ -1340,8 +1345,9 @@ UnicodeString __fastcall TRemoteFile::GetListingStr()
     LinkPart = UnicodeString(SYMLINKSTR) + LinkTo;
     LinkPart = UnicodeString(SYMLINKSTR) + LinkTo;
   }
   }
   return Format(L"%s%s %3s %-8s %-8s %9s %-12s %s%s", ARRAYOFCONST((
   return Format(L"%s%s %3s %-8s %-8s %9s %-12s %s%s", ARRAYOFCONST((
-    Type, Rights->Text, IntToStr(INodeBlocks), Owner.Name,
-    Group.Name, IntToStr(Size), ModificationStr, FileName,
+    Type, Rights->Text, IntToStr(INodeBlocks), Owner.Name, Group.Name,
+    IntToStr(FSize), // explicitly using size even for directories
+    ModificationStr, FileName,
     LinkPart)));
     LinkPart)));
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -1392,7 +1398,6 @@ __fastcall TRemoteDirectoryFile::TRemoteDirectoryFile() : TRemoteFile()
   ModificationFmt = mfNone;
   ModificationFmt = mfNone;
   LastAccess = Modification;
   LastAccess = Modification;
   Type = L'D';
   Type = L'D';
-  Size = 0;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -1462,7 +1467,7 @@ __int64 __fastcall TRemoteFileList::GetTotalSize()
 {
 {
   __int64 Result = 0;
   __int64 Result = 0;
   for (Integer Index = 0; Index < Count; Index++)
   for (Integer Index = 0; Index < Count; Index++)
-    if (!Files[Index]->IsDirectory) Result += Files[Index]->Size;
+    Result += Files[Index]->Size;
   return Result;
   return Result;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 2 - 1
source/core/RemoteFiles.h

@@ -131,6 +131,7 @@ private:
   UnicodeString __fastcall GetExtension();
   UnicodeString __fastcall GetExtension();
   UnicodeString __fastcall GetUserModificationStr();
   UnicodeString __fastcall GetUserModificationStr();
   void __fastcall LoadTypeInfo();
   void __fastcall LoadTypeInfo();
+  __int64 __fastcall GetSize() const;
 
 
 protected:
 protected:
   void __fastcall FindLinkedFile();
   void __fastcall FindLinkedFile();
@@ -147,7 +148,7 @@ public:
   __property bool BrokenLink = { read = GetBrokenLink };
   __property bool BrokenLink = { read = GetBrokenLink };
   __property TRemoteFileList * Directory = { read = FDirectory, write = FDirectory };
   __property TRemoteFileList * Directory = { read = FDirectory, write = FDirectory };
   __property UnicodeString RightsStr = { read = GetRightsStr };
   __property UnicodeString RightsStr = { read = GetRightsStr };
-  __property __int64 Size = { read = FSize, write = FSize };
+  __property __int64 Size = { read = GetSize, write = FSize };
   __property TRemoteToken Owner = { read = FOwner, write = FOwner };
   __property TRemoteToken Owner = { read = FOwner, write = FOwner };
   __property TRemoteToken Group = { read = FGroup, write = FGroup };
   __property TRemoteToken Group = { read = FGroup, write = FGroup };
   __property UnicodeString FileName = { read = FFileName, write = FFileName };
   __property UnicodeString FileName = { read = FFileName, write = FFileName };

+ 39 - 1
source/core/Script.cpp

@@ -376,6 +376,7 @@ void __fastcall TScript::Init()
   // the echo command does not have switches actually, but it must handle dashes in its arguments
   // the echo command does not have switches actually, but it must handle dashes in its arguments
   FCommands->Register(L"echo", SCRIPT_ECHO_DESC, SCRIPT_ECHO_HELP, &EchoProc, -1, -1, true);
   FCommands->Register(L"echo", SCRIPT_ECHO_DESC, SCRIPT_ECHO_HELP, &EchoProc, -1, -1, true);
   FCommands->Register(L"stat", SCRIPT_STAT_DESC, SCRIPT_STAT_HELP, &StatProc, 1, 1, false);
   FCommands->Register(L"stat", SCRIPT_STAT_DESC, SCRIPT_STAT_HELP, &StatProc, 1, 1, false);
+  FCommands->Register(L"checksum", SCRIPT_CHECKSUM_DESC, SCRIPT_CHECKSUM_HELP, &ChecksumProc, 2, 2, false);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TScript::CheckDefaultCopyParam()
 void __fastcall TScript::CheckDefaultCopyParam()
@@ -1057,6 +1058,40 @@ void __fastcall TScript::StatProc(TScriptProcParams * Parameters)
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TScript::ChecksumProc(TScriptProcParams * Parameters)
+{
+  CheckSession();
+  if (!FTerminal->IsCapable[fcCalculatingChecksum])
+  {
+    NotSupported();
+  }
+
+  UnicodeString Alg = Parameters->Param[1];
+  std::unique_ptr<TStrings> Checksums(new TStringList());
+  TStrings * FileList = CreateFileList(Parameters, 2, 2, fltQueryServer);
+  FTerminal->ExceptionOnFail = true;
+  try
+  {
+    if ((FileList->Count != 1) ||
+        NOT_NULL(dynamic_cast<TRemoteFile *>(FileList->Objects[0]))->IsDirectory)
+    {
+      throw Exception(FMTLOAD(NOT_FILE_ERROR, (FileList->Strings[0])));
+    }
+
+    FTerminal->CalculateFilesChecksum(Alg, FileList, Checksums.get(), NULL);
+
+    if (ALWAYS_TRUE(Checksums->Count == 1))
+    {
+      PrintLine(FORMAT(L"%s %s", (Checksums->Strings[0], FileList->Strings[0])));
+    }
+  }
+  __finally
+  {
+    FTerminal->ExceptionOnFail = false;
+    FreeFileList(FileList);
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TScript::TerminalCaptureLog(const UnicodeString & AddedLine,
 void __fastcall TScript::TerminalCaptureLog(const UnicodeString & AddedLine,
   TCaptureOutputType OutputType)
   TCaptureOutputType OutputType)
 {
 {
@@ -1071,6 +1106,7 @@ void __fastcall TScript::PwdProc(TScriptProcParams * /*Parameters*/)
   CheckSession();
   CheckSession();
 
 
   PrintLine(FTerminal->CurrentDirectory);
   PrintLine(FTerminal->CurrentDirectory);
+  TCwdSessionAction Action(FTerminal->ActionLog, FTerminal->CurrentDirectory);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TScript::CdProc(TScriptProcParams * Parameters)
 void __fastcall TScript::CdProc(TScriptProcParams * Parameters)
@@ -2137,7 +2173,9 @@ void __fastcall TManagementScript::TerminalOperationFinished(
   bool /*Temp*/, const UnicodeString & FileName, Boolean Success,
   bool /*Temp*/, const UnicodeString & FileName, Boolean Success,
   TOnceDoneOperation & /*OnceDoneOperation*/)
   TOnceDoneOperation & /*OnceDoneOperation*/)
 {
 {
-  if (Success && (Operation != foCalculateSize) && (Operation != foCopy) && (Operation != foMove))
+  if (Success &&
+      (Operation != foCalculateSize) && (Operation != foCalculateChecksum) &&
+      (Operation != foCopy) && (Operation != foMove))
   {
   {
     ShowPendingProgress();
     ShowPendingProgress();
     // For FKeepingUpToDate we should send events to synchronize controller eventually.
     // For FKeepingUpToDate we should send events to synchronize controller eventually.

+ 1 - 0
source/core/Script.h

@@ -144,6 +144,7 @@ protected:
   void __fastcall KeepUpToDateProc(TScriptProcParams * Parameters);
   void __fastcall KeepUpToDateProc(TScriptProcParams * Parameters);
   void __fastcall EchoProc(TScriptProcParams * Parameters);
   void __fastcall EchoProc(TScriptProcParams * Parameters);
   void __fastcall StatProc(TScriptProcParams * Parameters);
   void __fastcall StatProc(TScriptProcParams * Parameters);
+  void __fastcall ChecksumProc(TScriptProcParams * Parameters);
 
 
   void __fastcall OptionImpl(UnicodeString OptionName, UnicodeString ValueName);
   void __fastcall OptionImpl(UnicodeString OptionName, UnicodeString ValueName);
   void __fastcall SynchronizeDirectories(TScriptProcParams * Parameters,
   void __fastcall SynchronizeDirectories(TScriptProcParams * Parameters,

+ 39 - 8
source/core/SecureShell.cpp

@@ -372,7 +372,8 @@ void __fastcall TSecureShell::Open()
     try
     try
     {
     {
       InitError = FBackend->init(this, &FBackendHandle, conf,
       InitError = FBackend->init(this, &FBackendHandle, conf,
-        AnsiString(FSessionData->HostNameExpanded).c_str(), FSessionData->PortNumber, &RealHost, 0,
+        AnsiString(FSessionData->HostNameExpanded).c_str(), FSessionData->PortNumber, &RealHost,
+        (FSessionData->TcpNoDelay ? 1 : 0),
         conf_get_int(conf, CONF_tcp_keepalives));
         conf_get_int(conf, CONF_tcp_keepalives));
     }
     }
     __finally
     __finally
@@ -708,6 +709,7 @@ bool __fastcall TSecureShell::PromptUser(bool /*ToServer*/,
   }
   }
   else if (Index == 7)
   else if (Index == 7)
   {
   {
+    // Can be tested with WS_FTP server
     static const TPuttyTranslation NewPasswordPromptTranslation[] = {
     static const TPuttyTranslation NewPasswordPromptTranslation[] = {
       { L"Current password (blank for previously entered password): ", NEW_PASSWORD_CURRENT_PROMPT },
       { L"Current password (blank for previously entered password): ", NEW_PASSWORD_CURRENT_PROMPT },
       { L"Enter new password: ", NEW_PASSWORD_NEW_PROMPT },
       { L"Enter new password: ", NEW_PASSWORD_NEW_PROMPT },
@@ -1165,7 +1167,7 @@ void __fastcall TSecureShell::DispatchSendBuffer(int BufSize)
         "need to send at least another %u bytes",
         "need to send at least another %u bytes",
         (BufSize, BufSize - MAX_BUFSIZE)));
         (BufSize, BufSize - MAX_BUFSIZE)));
     }
     }
-    EventSelectLoop(100, false, NULL);
+    EventSelectLoop(100, false, true, NULL);
     BufSize = FBackend->sendbuffer(FBackendHandle);
     BufSize = FBackend->sendbuffer(FBackendHandle);
     if (Configuration->ActualLogProtocol >= 1)
     if (Configuration->ActualLogProtocol >= 1)
     {
     {
@@ -1210,7 +1212,7 @@ void __fastcall TSecureShell::Send(const unsigned char * Buf, Integer Len)
   }
   }
   FLastDataSent = Now();
   FLastDataSent = Now();
   // among other forces receive of pending data to free the servers's send buffer
   // among other forces receive of pending data to free the servers's send buffer
-  EventSelectLoop(0, false, NULL);
+  EventSelectLoop(0, false, true, NULL);
 
 
   if (BufSize > MAX_BUFSIZE)
   if (BufSize > MAX_BUFSIZE)
   {
   {
@@ -1607,7 +1609,7 @@ void __fastcall TSecureShell::PoolForData(WSANETWORKEVENTS & Events, unsigned in
       // in extreme condition it may happen that send buffer is full, but there
       // in extreme condition it may happen that send buffer is full, but there
       // will be no data coming and we may not empty the send buffer because we
       // will be no data coming and we may not empty the send buffer because we
       // do not process FD_WRITE until we receive any FD_READ
       // do not process FD_WRITE until we receive any FD_READ
-      if (EventSelectLoop(0, false, &Events))
+      if (EventSelectLoop(0, false, true, &Events))
       {
       {
         LogEvent(L"Data has arrived, closing query to user.");
         LogEvent(L"Data has arrived, closing query to user.");
         Result = qaOK;
         Result = qaOK;
@@ -1657,7 +1659,7 @@ void __fastcall TSecureShell::WaitForData()
       LogEvent(L"Looking for incoming data");
       LogEvent(L"Looking for incoming data");
     }
     }
 
 
-    IncomingData = EventSelectLoop(FSessionData->Timeout * MSecsPerSec, true, NULL);
+    IncomingData = EventSelectLoop(FSessionData->Timeout * MSecsPerSec, true, true, NULL);
     if (!IncomingData)
     if (!IncomingData)
     {
     {
       assert(FWaitingForData == 0);
       assert(FWaitingForData == 0);
@@ -1788,7 +1790,7 @@ bool __fastcall TSecureShell::ProcessNetworkEvents(SOCKET Socket)
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventRequired,
 bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventRequired,
-  WSANETWORKEVENTS * Events)
+  bool DoProcessGUI, WSANETWORKEVENTS * Events)
 {
 {
   CheckConnection();
   CheckConnection();
 
 
@@ -1820,7 +1822,10 @@ bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventR
         unsigned int TimeoutStep = std::min(GUIUpdateInterval, Timeout);
         unsigned int TimeoutStep = std::min(GUIUpdateInterval, Timeout);
         Timeout -= TimeoutStep;
         Timeout -= TimeoutStep;
         WaitResult = WaitForMultipleObjects(HandleCount + 1, Handles, FALSE, TimeoutStep);
         WaitResult = WaitForMultipleObjects(HandleCount + 1, Handles, FALSE, TimeoutStep);
-        ProcessGUI();
+        if (DoProcessGUI)
+        {
+          ProcessGUI();
+        }
       } while ((WaitResult == WAIT_TIMEOUT) && (Timeout > 0));
       } while ((WaitResult == WAIT_TIMEOUT) && (Timeout > 0));
 
 
       if (WaitResult < WAIT_OBJECT_0 + HandleCount)
       if (WaitResult < WAIT_OBJECT_0 + HandleCount)
@@ -1917,7 +1922,9 @@ void __fastcall TSecureShell::Idle(unsigned int MSec)
   // do not read here, otherwise we swallow read event and never wake
   // do not read here, otherwise we swallow read event and never wake
   if (FWaitingForData <= 0)
   if (FWaitingForData <= 0)
   {
   {
-    EventSelectLoop(MSec, false, NULL);
+    // do not process GUI here, as we are called from GUI loop and may
+    // recurse for good
+    EventSelectLoop(MSec, false, false, NULL);
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -2299,6 +2306,30 @@ void __fastcall TSecureShell::CollectUsage()
   {
   {
     Configuration->Usage->Inc(L"OpenedSessionsSSHTitan");
     Configuration->Usage->Inc(L"OpenedSessionsSSHTitan");
   }
   }
+  else if (ContainsText(FSessionInfo.SshImplementation, L"Serv-U"))
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHServU");
+  }
+  else if (ContainsText(FSessionInfo.SshImplementation, L"CerberusFTPServer"))
+  {
+    // Ntb, Cerberus can also be detected using vendor-id extension
+    // Cerberus FTP Server 7.0.5.3 (70005003) by Cerberus, LLC
+    Configuration->Usage->Inc(L"OpenedSessionsSSHCerberus");
+  }
+  else if (ContainsText(FSessionInfo.SshImplementation, L"WS_FTP"))
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHWSFTP");
+  }
+  // SSH-2.0-1.36_sshlib GlobalSCAPE
+  else if (ContainsText(FSessionInfo.SshImplementation, L"GlobalSCAPE"))
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHGlobalScape");
+  }
+  // SSH-2.0-CompleteFTP-8.1.3
+  else if (ContainsText(FSessionInfo.SshImplementation, L"CompleteFTP"))
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHComplete");
+  }
   else
   else
   {
   {
     Configuration->Usage->Inc(L"OpenedSessionsSSHOther");
     Configuration->Usage->Inc(L"OpenedSessionsSSHOther");

+ 1 - 1
source/core/SecureShell.h

@@ -87,7 +87,7 @@ private:
   void __fastcall HandleNetworkEvents(SOCKET Socket, WSANETWORKEVENTS & Events);
   void __fastcall HandleNetworkEvents(SOCKET Socket, WSANETWORKEVENTS & Events);
   bool __fastcall ProcessNetworkEvents(SOCKET Socket);
   bool __fastcall ProcessNetworkEvents(SOCKET Socket);
   bool __fastcall EventSelectLoop(unsigned int MSec, bool ReadEventRequired,
   bool __fastcall EventSelectLoop(unsigned int MSec, bool ReadEventRequired,
-    WSANETWORKEVENTS * Events);
+    bool DoProcessGUI, WSANETWORKEVENTS * Events);
   void __fastcall UpdateSessionInfo();
   void __fastcall UpdateSessionInfo();
   bool __fastcall GetReady();
   bool __fastcall GetReady();
   void __fastcall DispatchSendBuffer(int BufSize);
   void __fastcall DispatchSendBuffer(int BufSize);

+ 135 - 33
source/core/SessionData.cpp

@@ -102,7 +102,7 @@ void __fastcall TSessionData::Default()
   PublicKeyFile = L"";
   PublicKeyFile = L"";
   Passphrase = L"";
   Passphrase = L"";
   FPuttyProtocol = PuttySshProtocol;
   FPuttyProtocol = PuttySshProtocol;
-  TcpNoDelay = true;
+  TcpNoDelay = false;
   SendBuf = DefaultSendBuf;
   SendBuf = DefaultSendBuf;
   SshSimple = true;
   SshSimple = true;
   HostKey = L"";
   HostKey = L"";
@@ -198,6 +198,7 @@ void __fastcall TSessionData::Default()
   MinTlsVersion = tls10;
   MinTlsVersion = tls10;
   MaxTlsVersion = tls12;
   MaxTlsVersion = tls12;
   FtpListAll = asAuto;
   FtpListAll = asAuto;
+  FtpHost = asAuto;
   SslSessionReuse = true;
   SslSessionReuse = true;
 
 
   FtpProxyLogonType = 0; // none
   FtpProxyLogonType = 0; // none
@@ -338,6 +339,7 @@ void __fastcall TSessionData::NonPersistant()
   PROPERTY(FtpPingType); \
   PROPERTY(FtpPingType); \
   PROPERTY(FtpTransferActiveImmediately); \
   PROPERTY(FtpTransferActiveImmediately); \
   PROPERTY(FtpListAll); \
   PROPERTY(FtpListAll); \
+  PROPERTY(FtpHost); \
   PROPERTY(SslSessionReuse); \
   PROPERTY(SslSessionReuse); \
   \
   \
   PROPERTY(FtpProxyLogonType); \
   PROPERTY(FtpProxyLogonType); \
@@ -378,6 +380,28 @@ void __fastcall TSessionData::CopyData(TSessionData * SourceData)
   FSaveOnly = SourceData->FSaveOnly;
   FSaveOnly = SourceData->FSaveOnly;
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
+void __fastcall TSessionData::CopyDirectoriesStateData(TSessionData * SourceData)
+{
+  RemoteDirectory = SourceData->RemoteDirectory;
+  LocalDirectory = SourceData->LocalDirectory;
+  SynchronizeBrowsing = SourceData->SynchronizeBrowsing;
+}
+//---------------------------------------------------------------------
+bool __fastcall TSessionData::HasStateData()
+{
+  return
+    !RemoteDirectory.IsEmpty() ||
+    !LocalDirectory.IsEmpty() ||
+    (Color != 0);
+}
+//---------------------------------------------------------------------
+void __fastcall TSessionData::CopyStateData(TSessionData * SourceData)
+{
+  // Keep in sync with TCustomScpExplorerForm::UpdateSessionData.
+  CopyDirectoriesStateData(SourceData);
+  Color = SourceData->Color;
+}
+//---------------------------------------------------------------------
 bool __fastcall TSessionData::IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties)
 bool __fastcall TSessionData::IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties)
 {
 {
   bool Result = true;
   bool Result = true;
@@ -614,6 +638,7 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool & Rewr
   FtpTransferActiveImmediately = Storage->ReadBool(L"FtpTransferActiveImmediately", FtpTransferActiveImmediately);
   FtpTransferActiveImmediately = Storage->ReadBool(L"FtpTransferActiveImmediately", FtpTransferActiveImmediately);
   Ftps = static_cast<TFtps>(Storage->ReadInteger(L"Ftps", Ftps));
   Ftps = static_cast<TFtps>(Storage->ReadInteger(L"Ftps", Ftps));
   FtpListAll = TAutoSwitch(Storage->ReadInteger(L"FtpListAll", FtpListAll));
   FtpListAll = TAutoSwitch(Storage->ReadInteger(L"FtpListAll", FtpListAll));
+  FtpHost = TAutoSwitch(Storage->ReadInteger(L"FtpHost", FtpHost));
   SslSessionReuse = Storage->ReadBool(L"SslSessionReuse", SslSessionReuse);
   SslSessionReuse = Storage->ReadBool(L"SslSessionReuse", SslSessionReuse);
 
 
   FtpProxyLogonType = Storage->ReadInteger(L"FtpProxyLogonType", FtpProxyLogonType);
   FtpProxyLogonType = Storage->ReadInteger(L"FtpProxyLogonType", FtpProxyLogonType);
@@ -869,6 +894,7 @@ void __fastcall TSessionData::Save(THierarchicalStorage * Storage,
       WRITE_DATA(Bool, FtpTransferActiveImmediately);
       WRITE_DATA(Bool, FtpTransferActiveImmediately);
       WRITE_DATA(Integer, Ftps);
       WRITE_DATA(Integer, Ftps);
       WRITE_DATA(Integer, FtpListAll);
       WRITE_DATA(Integer, FtpListAll);
+      WRITE_DATA(Integer, FtpHost);
       WRITE_DATA(Bool, SslSessionReuse);
       WRITE_DATA(Bool, SslSessionReuse);
 
 
       WRITE_DATA(Integer, FtpProxyLogonType);
       WRITE_DATA(Integer, FtpProxyLogonType);
@@ -1165,7 +1191,7 @@ void __fastcall TSessionData::CacheHostKeyIfNotCached()
     UnicodeString HostKeyName = PuttyMungeStr(FORMAT(L"%s@%d:%s", (KeyType, PortNumber, HostName)));
     UnicodeString HostKeyName = PuttyMungeStr(FORMAT(L"%s@%d:%s", (KeyType, PortNumber, HostName)));
     if (!Storage->ValueExists(HostKeyName))
     if (!Storage->ValueExists(HostKeyName))
     {
     {
-      // fingerprint is MD5 of host key, so it cannot be translate back to host key,
+      // fingerprint is MD5 of host key, so it cannot be translated back to host key,
       // so we store fingerprint and TSecureShell::VerifyHostKey was
       // so we store fingerprint and TSecureShell::VerifyHostKey was
       // modified to accept also fingerprint
       // modified to accept also fingerprint
       Storage->WriteString(HostKeyName, HostKey);
       Storage->WriteString(HostKeyName, HostKey);
@@ -2069,6 +2095,8 @@ UnicodeString __fastcall TSessionData::GetDefaultSessionName()
 {
 {
   if (!HostName.IsEmpty() && !UserName.IsEmpty())
   if (!HostName.IsEmpty() && !UserName.IsEmpty())
   {
   {
+    // If we ever choose to include port number,
+    // we have to escape IPv6 literals in HostName
     return FORMAT(L"%s@%s", (UserName, HostName));
     return FORMAT(L"%s@%s", (UserName, HostName));
   }
   }
   else if (!HostName.IsEmpty())
   else if (!HostName.IsEmpty())
@@ -2081,9 +2109,19 @@ UnicodeString __fastcall TSessionData::GetDefaultSessionName()
   }
   }
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
+UnicodeString __fastcall TSessionData::GetNameWithoutHiddenPrefix()
+{
+  UnicodeString Result = Name;
+  if (Hidden)
+  {
+    Result = Result.SubString(TNamedObjectList::HiddenPrefix.Length() + 1, Result.Length() - TNamedObjectList::HiddenPrefix.Length());
+  }
+  return Result;
+}
+//---------------------------------------------------------------------
 bool __fastcall TSessionData::HasSessionName()
 bool __fastcall TSessionData::HasSessionName()
 {
 {
-  return (!Name.IsEmpty() && (Name != DefaultName) && !IsWorkspace);
+  return (!GetNameWithoutHiddenPrefix().IsEmpty() && (Name != DefaultName) && !IsWorkspace);
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
 UnicodeString __fastcall TSessionData::GetSessionName()
 UnicodeString __fastcall TSessionData::GetSessionName()
@@ -2091,11 +2129,7 @@ UnicodeString __fastcall TSessionData::GetSessionName()
   UnicodeString Result;
   UnicodeString Result;
   if (HasSessionName())
   if (HasSessionName())
   {
   {
-    Result = Name;
-    if (Hidden)
-    {
-      Result = Result.SubString(TNamedObjectList::HiddenPrefix.Length() + 1, Result.Length() - TNamedObjectList::HiddenPrefix.Length());
-    }
+    Result = GetNameWithoutHiddenPrefix();
   }
   }
   else
   else
   {
   {
@@ -2149,30 +2183,66 @@ UnicodeString __fastcall TSessionData::GetProtocolUrl()
   return Url;
   return Url;
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
-UnicodeString __fastcall TSessionData::GetSessionUrl()
+static bool __fastcall IsIPv6Literal(const UnicodeString & HostName)
+{
+  bool Result = (HostName.Pos(L":") > 0);
+  if (Result)
+  {
+    for (int Index = 1; Result && (Index <= HostName.Length()); Index++)
+    {
+      wchar_t C = HostName[Index];
+      Result = IsHex(C) || (C == L':');
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------
+UnicodeString __fastcall TSessionData::GenerateSessionUrl(unsigned int Flags)
 {
 {
   UnicodeString Url;
   UnicodeString Url;
-  if (HasSessionName())
+
+  if (FLAGSET(Flags, sufSpecific))
   {
   {
-    Url = Name;
+    Url += WinSCPProtocolPrefix;
   }
   }
-  else
+
+  Url += GetProtocolUrl();
+
+  if (FLAGSET(Flags, sufUserName) && !UserNameExpanded.IsEmpty())
   {
   {
-    Url = ProtocolUrl;
+    Url += EncodeUrlString(UserNameExpanded);
 
 
-    if (!HostName.IsEmpty() && !UserName.IsEmpty())
-    {
-      Url += FORMAT(L"%s@%s", (UserName, HostName));
-    }
-    else if (!HostName.IsEmpty())
+    if (FLAGSET(Flags, sufPassword) && !Password.IsEmpty())
     {
     {
-      Url += HostName;
+      Url += L":" + EncodeUrlString(Password);
     }
     }
-    else
+
+    if (FLAGSET(Flags, sufHostKey) && !HostKey.IsEmpty())
     {
     {
-      Url = L"";
+      Url +=
+        UnicodeString(UrlParamSeparator) + UrlHostKeyParamName +
+        UnicodeString(UrlParamValueSeparator) + NormalizeFingerprint(HostKey);
     }
     }
+
+    Url += L"@";
+  }
+
+  assert(!HostNameExpanded.IsEmpty());
+  if (IsIPv6Literal(HostNameExpanded))
+  {
+    Url += L"[" + HostNameExpanded + L"]";
+  }
+  else
+  {
+    Url += EncodeUrlString(HostNameExpanded);
   }
   }
+
+  if (PortNumber != DefaultPort(FSProtocol, Ftps))
+  {
+    Url += L":" + IntToStr(PortNumber);
+  }
+  Url += L"/";
+
   return Url;
   return Url;
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
@@ -2550,6 +2620,11 @@ void __fastcall TSessionData::SetFtpListAll(TAutoSwitch value)
   SET_SESSION_PROPERTY(FtpListAll);
   SET_SESSION_PROPERTY(FtpListAll);
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
+void __fastcall TSessionData::SetFtpHost(TAutoSwitch value)
+{
+  SET_SESSION_PROPERTY(FtpHost);
+}
+//---------------------------------------------------------------------
 void __fastcall TSessionData::SetSslSessionReuse(bool value)
 void __fastcall TSessionData::SetSslSessionReuse(bool value)
 {
 {
   SET_SESSION_PROPERTY(SslSessionReuse);
   SET_SESSION_PROPERTY(SslSessionReuse);
@@ -3009,9 +3084,7 @@ void __fastcall TStoredSessionList::UpdateStaticUsage()
   bool Folders = false;
   bool Folders = false;
   bool Workspaces = false;
   bool Workspaces = false;
   std::unique_ptr<TSessionData> FactoryDefaults(new TSessionData(L""));
   std::unique_ptr<TSessionData> FactoryDefaults(new TSessionData(L""));
-  std::unique_ptr<TStringList> DifferentAdvancedProperties(new TStringList(L""));
-  DifferentAdvancedProperties->Sorted = true;
-  DifferentAdvancedProperties->Duplicates = Types::dupIgnore;
+  std::unique_ptr<TStringList> DifferentAdvancedProperties(CreateSortedStringList());
   for (int Index = 0; Index < Count; Index++)
   for (int Index = 0; Index < Count; Index++)
   {
   {
     TSessionData * Data = Sessions[Index];
     TSessionData * Data = Sessions[Index];
@@ -3273,7 +3346,7 @@ TSessionData * __fastcall TStoredSessionList::CheckIsInFolderOrWorkspaceAndResol
 {
 {
   if (Data->IsInFolderOrWorkspace(Name))
   if (Data->IsInFolderOrWorkspace(Name))
   {
   {
-    Data = ResolveSessionData(Data);
+    Data = ResolveWorkspaceData(Data);
 
 
     if ((Data != NULL) && Data->CanLogin &&
     if ((Data != NULL) && Data->CanLogin &&
         ALWAYS_TRUE(Data->Link.IsEmpty()))
         ALWAYS_TRUE(Data->Link.IsEmpty()))
@@ -3288,13 +3361,25 @@ void __fastcall TStoredSessionList::GetFolderOrWorkspace(const UnicodeString & N
 {
 {
   for (int Index = 0; (Index < Count); Index++)
   for (int Index = 0; (Index < Count); Index++)
   {
   {
+    TSessionData * RawData = Sessions[Index];
     TSessionData * Data =
     TSessionData * Data =
-      CheckIsInFolderOrWorkspaceAndResolve(Sessions[Index], Name);
+      CheckIsInFolderOrWorkspaceAndResolve(RawData, Name);
 
 
     if (Data != NULL)
     if (Data != NULL)
     {
     {
       TSessionData * Data2 = new TSessionData(L"");
       TSessionData * Data2 = new TSessionData(L"");
       Data2->Assign(Data);
       Data2->Assign(Data);
+
+      if (!RawData->Link.IsEmpty() && (ALWAYS_TRUE(Data != RawData)) &&
+          // BACKWARD COMPATIBILITY
+          // When loading pre-5.6.4 workspace, that does not have state saved,
+          // do not overwrite the site "state" defaults
+          // with (empty) workspace state
+          RawData->HasStateData())
+      {
+        Data2->CopyStateData(RawData);
+      }
+
       List->Add(Data2);
       List->Add(Data2);
     }
     }
   }
   }
@@ -3321,10 +3406,7 @@ TStrings * __fastcall TStoredSessionList::GetFolderOrWorkspaceList(
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 TStrings * __fastcall TStoredSessionList::GetWorkspaces()
 TStrings * __fastcall TStoredSessionList::GetWorkspaces()
 {
 {
-  std::unique_ptr<TStringList> Result(new TStringList());
-  Result->Sorted = true;
-  Result->Duplicates = Types::dupIgnore;
-  Result->CaseSensitive = false;
+  std::unique_ptr<TStringList> Result(CreateSortedStringList());
 
 
   for (int Index = 0; (Index < Count); Index++)
   for (int Index = 0; (Index < Count); Index++)
   {
   {
@@ -3403,22 +3485,42 @@ bool __fastcall TStoredSessionList::IsUrl(UnicodeString Url)
   return Result;
   return Result;
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
-TSessionData * __fastcall TStoredSessionList::ResolveSessionData(TSessionData * Data)
+TSessionData * __fastcall TStoredSessionList::ResolveWorkspaceData(TSessionData * Data)
 {
 {
   if (!Data->Link.IsEmpty())
   if (!Data->Link.IsEmpty())
   {
   {
     Data = dynamic_cast<TSessionData *>(FindByName(Data->Link));
     Data = dynamic_cast<TSessionData *>(FindByName(Data->Link));
     if (Data != NULL)
     if (Data != NULL)
     {
     {
-      Data = ResolveSessionData(Data);
+      Data = ResolveWorkspaceData(Data);
     }
     }
   }
   }
   return Data;
   return Data;
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
+TSessionData * __fastcall TStoredSessionList::SaveWorkspaceData(TSessionData * Data)
+{
+  std::unique_ptr<TSessionData> Result(new TSessionData(L""));
+
+  TSessionData * SameData = StoredSessions->FindSame(Data);
+  if (SameData != NULL)
+  {
+    Result->CopyStateData(Data);
+    Result->Link = Data->Name;
+  }
+  else
+  {
+    Result->Assign(Data);
+  }
+
+  Result->IsWorkspace = true;
+
+  return Result.release();
+}
+//---------------------------------------------------------------------
 bool __fastcall TStoredSessionList::CanLogin(TSessionData * Data)
 bool __fastcall TStoredSessionList::CanLogin(TSessionData * Data)
 {
 {
-  Data = ResolveSessionData(Data);
+  Data = ResolveWorkspaceData(Data);
   return (Data != NULL) && Data->CanLogin;
   return (Data != NULL) && Data->CanLogin;
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------

+ 18 - 4
source/core/SessionData.h

@@ -33,6 +33,14 @@ enum TFtps { ftpsNone, ftpsImplicit, ftpsExplicitSsl, ftpsExplicitTls };
 // has to match SSL_VERSION_XXX constants in AsyncSslSocketLayer.h
 // has to match SSL_VERSION_XXX constants in AsyncSslSocketLayer.h
 enum TTlsVersion { ssl2 = 2, ssl3 = 3, tls10 = 10, tls11 = 11, tls12 = 12 };
 enum TTlsVersion { ssl2 = 2, ssl3 = 3, tls10 = 10, tls11 = 11, tls12 = 12 };
 enum TSessionSource { ssNone, ssStored, ssStoredModified };
 enum TSessionSource { ssNone, ssStored, ssStoredModified };
+enum TSessionUrlFlags
+{
+  sufSpecific = 0x01,
+  sufUserName = 0x02,
+  sufPassword = 0x04,
+  sufHostKey = 0x08,
+  sufComplete = sufUserName | sufPassword | sufHostKey
+};
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 extern const wchar_t CipherNames[CIPHER_COUNT][10];
 extern const wchar_t CipherNames[CIPHER_COUNT][10];
 extern const wchar_t KexNames[KEX_COUNT][20];
 extern const wchar_t KexNames[KEX_COUNT][20];
@@ -152,6 +160,7 @@ private:
   UnicodeString FPostLoginCommands;
   UnicodeString FPostLoginCommands;
   TAutoSwitch FSCPLsFullTime;
   TAutoSwitch FSCPLsFullTime;
   TAutoSwitch FFtpListAll;
   TAutoSwitch FFtpListAll;
+  TAutoSwitch FFtpHost;
   bool FSslSessionReuse;
   bool FSslSessionReuse;
   TAddressFamily FAddressFamily;
   TAddressFamily FAddressFamily;
   UnicodeString FRekeyData;
   UnicodeString FRekeyData;
@@ -230,7 +239,6 @@ private:
   UnicodeString __fastcall GetSessionName();
   UnicodeString __fastcall GetSessionName();
   bool __fastcall HasSessionName();
   bool __fastcall HasSessionName();
   UnicodeString __fastcall GetDefaultSessionName();
   UnicodeString __fastcall GetDefaultSessionName();
-  UnicodeString __fastcall GetSessionUrl();
   UnicodeString __fastcall GetProtocolUrl();
   UnicodeString __fastcall GetProtocolUrl();
   void __fastcall SetFSProtocol(TFSProtocol value);
   void __fastcall SetFSProtocol(TFSProtocol value);
   UnicodeString __fastcall GetFSProtocolStr();
   UnicodeString __fastcall GetFSProtocolStr();
@@ -294,6 +302,7 @@ private:
   TAutoSwitch __fastcall GetSFTPBug(TSftpBug Bug) const;
   TAutoSwitch __fastcall GetSFTPBug(TSftpBug Bug) const;
   void __fastcall SetSCPLsFullTime(TAutoSwitch value);
   void __fastcall SetSCPLsFullTime(TAutoSwitch value);
   void __fastcall SetFtpListAll(TAutoSwitch value);
   void __fastcall SetFtpListAll(TAutoSwitch value);
+  void __fastcall SetFtpHost(TAutoSwitch value);
   void __fastcall SetSslSessionReuse(bool value);
   void __fastcall SetSslSessionReuse(bool value);
   UnicodeString __fastcall GetStorageKey();
   UnicodeString __fastcall GetStorageKey();
   UnicodeString __fastcall GetInternalStorageKey();
   UnicodeString __fastcall GetInternalStorageKey();
@@ -343,6 +352,9 @@ private:
   UnicodeString __fastcall ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, const UnicodeString & Default);
   UnicodeString __fastcall ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, const UnicodeString & Default);
   int __fastcall ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, int Default);
   int __fastcall ReadXmlNode(_di_IXMLNode Node, const UnicodeString & Name, int Default);
   bool __fastcall IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties);
   bool __fastcall IsSame(const TSessionData * Default, bool AdvancedOnly, TStrings * DifferentProperties);
+  UnicodeString __fastcall GetNameWithoutHiddenPrefix();
+  bool __fastcall HasStateData();
+  void __fastcall CopyStateData(TSessionData * SourceData);
   static RawByteString __fastcall EncryptPassword(const UnicodeString & Password, UnicodeString Key);
   static RawByteString __fastcall EncryptPassword(const UnicodeString & Password, UnicodeString Key);
   static UnicodeString __fastcall DecryptPassword(const RawByteString & Password, UnicodeString Key);
   static UnicodeString __fastcall DecryptPassword(const RawByteString & Password, UnicodeString Key);
   static RawByteString __fastcall StronglyRecryptPassword(const RawByteString & Password, UnicodeString Key);
   static RawByteString __fastcall StronglyRecryptPassword(const RawByteString & Password, UnicodeString Key);
@@ -371,6 +383,7 @@ public:
   void __fastcall CacheHostKeyIfNotCached();
   void __fastcall CacheHostKeyIfNotCached();
   virtual void __fastcall Assign(TPersistent * Source);
   virtual void __fastcall Assign(TPersistent * Source);
   void __fastcall CopyData(TSessionData * Source);
   void __fastcall CopyData(TSessionData * Source);
+  void __fastcall CopyDirectoriesStateData(TSessionData * SourceData);
   bool __fastcall ParseUrl(UnicodeString Url, TOptions * Options,
   bool __fastcall ParseUrl(UnicodeString Url, TOptions * Options,
     TStoredSessionList * StoredSessions, bool & DefaultsOnly,
     TStoredSessionList * StoredSessions, bool & DefaultsOnly,
     UnicodeString * FileName, bool * AProtocolDefined, UnicodeString * MaskedUrl);
     UnicodeString * FileName, bool * AProtocolDefined, UnicodeString * MaskedUrl);
@@ -381,6 +394,7 @@ public:
   bool __fastcall IsSame(const TSessionData * Default, bool AdvancedOnly);
   bool __fastcall IsSame(const TSessionData * Default, bool AdvancedOnly);
   bool __fastcall IsSameSite(const TSessionData * Default);
   bool __fastcall IsSameSite(const TSessionData * Default);
   bool __fastcall IsInFolderOrWorkspace(UnicodeString Name);
   bool __fastcall IsInFolderOrWorkspace(UnicodeString Name);
+  UnicodeString __fastcall GenerateSessionUrl(unsigned int Flags);
   static void __fastcall ValidatePath(const UnicodeString Path);
   static void __fastcall ValidatePath(const UnicodeString Path);
   static void __fastcall ValidateName(const UnicodeString Name);
   static void __fastcall ValidateName(const UnicodeString Name);
   static UnicodeString __fastcall MakeValidName(const UnicodeString & Name);
   static UnicodeString __fastcall MakeValidName(const UnicodeString & Name);
@@ -426,8 +440,6 @@ public:
   __property TPingType PingType = { read = FPingType, write = SetPingType };
   __property TPingType PingType = { read = FPingType, write = SetPingType };
   __property UnicodeString SessionName  = { read=GetSessionName };
   __property UnicodeString SessionName  = { read=GetSessionName };
   __property UnicodeString DefaultSessionName  = { read=GetDefaultSessionName };
   __property UnicodeString DefaultSessionName  = { read=GetDefaultSessionName };
-  __property UnicodeString SessionUrl  = { read=GetSessionUrl };
-  __property UnicodeString ProtocolUrl  = { read=GetProtocolUrl };
   __property UnicodeString LocalDirectory  = { read=FLocalDirectory, write=SetLocalDirectory };
   __property UnicodeString LocalDirectory  = { read=FLocalDirectory, write=SetLocalDirectory };
   __property UnicodeString RemoteDirectory  = { read=FRemoteDirectory, write=SetRemoteDirectory };
   __property UnicodeString RemoteDirectory  = { read=FRemoteDirectory, write=SetRemoteDirectory };
   __property bool SynchronizeBrowsing = { read=FSynchronizeBrowsing, write=SetSynchronizeBrowsing };
   __property bool SynchronizeBrowsing = { read=FSynchronizeBrowsing, write=SetSynchronizeBrowsing };
@@ -480,6 +492,7 @@ public:
   __property TAutoSwitch SFTPBug[TSftpBug Bug]  = { read=GetSFTPBug, write=SetSFTPBug };
   __property TAutoSwitch SFTPBug[TSftpBug Bug]  = { read=GetSFTPBug, write=SetSFTPBug };
   __property TAutoSwitch SCPLsFullTime = { read = FSCPLsFullTime, write = SetSCPLsFullTime };
   __property TAutoSwitch SCPLsFullTime = { read = FSCPLsFullTime, write = SetSCPLsFullTime };
   __property TAutoSwitch FtpListAll = { read = FFtpListAll, write = SetFtpListAll };
   __property TAutoSwitch FtpListAll = { read = FFtpListAll, write = SetFtpListAll };
+  __property TAutoSwitch FtpHost = { read = FFtpHost, write = SetFtpHost };
   __property bool SslSessionReuse = { read = FSslSessionReuse, write = SetSslSessionReuse };
   __property bool SslSessionReuse = { read = FSslSessionReuse, write = SetSslSessionReuse };
   __property TDSTMode DSTMode = { read = FDSTMode, write = SetDSTMode };
   __property TDSTMode DSTMode = { read = FDSTMode, write = SetDSTMode };
   __property bool DeleteToRecycleBin = { read = FDeleteToRecycleBin, write = SetDeleteToRecycleBin };
   __property bool DeleteToRecycleBin = { read = FDeleteToRecycleBin, write = SetDeleteToRecycleBin };
@@ -561,6 +574,7 @@ public:
   TStrings * __fastcall GetFolderOrWorkspaceList(const UnicodeString & Name);
   TStrings * __fastcall GetFolderOrWorkspaceList(const UnicodeString & Name);
   TStrings * __fastcall GetWorkspaces();
   TStrings * __fastcall GetWorkspaces();
   bool __fastcall HasAnyWorkspace();
   bool __fastcall HasAnyWorkspace();
+  TSessionData * __fastcall SaveWorkspaceData(TSessionData * Data);
   virtual __fastcall ~TStoredSessionList();
   virtual __fastcall ~TStoredSessionList();
   __property TSessionData * Sessions[int Index]  = { read=AtSession };
   __property TSessionData * Sessions[int Index]  = { read=AtSession };
   __property TSessionData * DefaultSettings  = { read=FDefaultSettings, write=SetDefaultSettings };
   __property TSessionData * DefaultSettings  = { read=FDefaultSettings, write=SetDefaultSettings };
@@ -580,7 +594,7 @@ private:
   void __fastcall DoSave(THierarchicalStorage * Storage,
   void __fastcall DoSave(THierarchicalStorage * Storage,
     TSessionData * Data, bool All, bool RecryptPasswordOnly,
     TSessionData * Data, bool All, bool RecryptPasswordOnly,
     TSessionData * FactoryDefaults);
     TSessionData * FactoryDefaults);
-  TSessionData * __fastcall ResolveSessionData(TSessionData * Data);
+  TSessionData * __fastcall ResolveWorkspaceData(TSessionData * Data);
   bool __fastcall IsFolderOrWorkspace(const UnicodeString & Name, bool Workspace);
   bool __fastcall IsFolderOrWorkspace(const UnicodeString & Name, bool Workspace);
   TSessionData * __fastcall CheckIsInFolderOrWorkspaceAndResolve(
   TSessionData * __fastcall CheckIsInFolderOrWorkspaceAndResolve(
     TSessionData * Data, const UnicodeString & Name);
     TSessionData * Data, const UnicodeString & Name);

+ 48 - 3
source/core/SessionInfo.cpp

@@ -156,6 +156,14 @@ public:
             }
             }
             FLog->AddIndented(FORMAT(L"      <modification value=\"%s\" />", (StandardTimestamp(File->Modification))));
             FLog->AddIndented(FORMAT(L"      <modification value=\"%s\" />", (StandardTimestamp(File->Modification))));
             FLog->AddIndented(FORMAT(L"      <permissions value=\"%s\" />", (XmlAttributeEscape(File->Rights->Text))));
             FLog->AddIndented(FORMAT(L"      <permissions value=\"%s\" />", (XmlAttributeEscape(File->Rights->Text))));
+            if (File->Owner.IsSet)
+            {
+              FLog->AddIndented(FORMAT(L"      <owner value=\"%s\" />", (XmlAttributeEscape(File->Owner.DisplayText))));
+            }
+            if (File->Group.IsSet)
+            {
+              FLog->AddIndented(FORMAT(L"      <group value=\"%s\" />", (XmlAttributeEscape(File->Group.DisplayText))));
+            }
             FLog->AddIndented(L"    </file>");
             FLog->AddIndented(L"    </file>");
           }
           }
           FLog->AddIndented(L"  </files>");
           FLog->AddIndented(L"  </files>");
@@ -257,11 +265,22 @@ public:
     }
     }
   }
   }
 
 
-  void __fastcall AddExitCode(int ExitCode)
+  void __fastcall ExitCode(int ExitCode)
   {
   {
     Parameter(L"exitcode", IntToStr(ExitCode));
     Parameter(L"exitcode", IntToStr(ExitCode));
   }
   }
 
 
+  void __fastcall Checksum(const UnicodeString & Alg, const UnicodeString & Checksum)
+  {
+    Parameter(L"algorithm", Alg);
+    Parameter(L"checksum", Checksum);
+  }
+
+  void __fastcall Cwd(const UnicodeString & Path)
+  {
+    Parameter(L"cwd", Path);
+  }
+
   void __fastcall FileList(TRemoteFileList * FileList)
   void __fastcall FileList(TRemoteFileList * FileList)
   {
   {
     if (FFileList == NULL)
     if (FFileList == NULL)
@@ -304,6 +323,8 @@ protected:
       case laCall: return L"call";
       case laCall: return L"call";
       case laLs: return L"ls";
       case laLs: return L"ls";
       case laStat: return L"stat";
       case laStat: return L"stat";
+      case laChecksum: return L"checksum";
+      case laCwd: return L"cwd";
       default: FAIL; return L"";
       default: FAIL; return L"";
     }
     }
   }
   }
@@ -526,11 +547,11 @@ void __fastcall TCallSessionAction::AddOutput(const UnicodeString & Output, bool
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-void __fastcall TCallSessionAction::AddExitCode(int ExitCode)
+void __fastcall TCallSessionAction::ExitCode(int ExitCode)
 {
 {
   if (FRecord != NULL)
   if (FRecord != NULL)
   {
   {
-    FRecord->AddExitCode(ExitCode);
+    FRecord->ExitCode(ExitCode);
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -567,6 +588,30 @@ void __fastcall TStatSessionAction::File(TRemoteFile * File)
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+__fastcall TChecksumSessionAction::TChecksumSessionAction(TActionLog * Log) :
+  TFileSessionAction(Log, laChecksum)
+{
+}
+//---------------------------------------------------------------------------
+void __fastcall TChecksumSessionAction::Checksum(const UnicodeString & Alg, const UnicodeString & Checksum)
+{
+  if (FRecord != NULL)
+  {
+    FRecord->Checksum(Alg, Checksum);
+  }
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+__fastcall TCwdSessionAction::TCwdSessionAction(TActionLog * Log, const UnicodeString & Path) :
+  TSessionAction(Log, laCwd)
+{
+  if (FRecord != NULL)
+  {
+    FRecord->Cwd(Path);
+  }
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 TSessionInfo::TSessionInfo()
 TSessionInfo::TSessionInfo()
 {
 {
   LoginTime = Now();
   LoginTime = Now();

+ 20 - 2
source/core/SessionInfo.h

@@ -75,7 +75,11 @@ public:
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 // Duplicated in LogMemo.h for design-time-only purposes
 // Duplicated in LogMemo.h for design-time-only purposes
 enum TLogLineType { llOutput, llInput, llStdError, llMessage, llException };
 enum TLogLineType { llOutput, llInput, llStdError, llMessage, llException };
-enum TLogAction { laUpload, laDownload, laTouch, laChmod, laMkdir, laRm, laMv, laCall, laLs, laStat };
+enum TLogAction
+{
+  laUpload, laDownload, laTouch, laChmod, laMkdir, laRm, laMv, laCall, laLs,
+  laStat, laChecksum, laCwd
+};
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 enum TCaptureOutputType { cotOutput, cotError, cotExitCode };
 enum TCaptureOutputType { cotOutput, cotError, cotExitCode };
 typedef void __fastcall (__closure *TCaptureOutputEvent)(
 typedef void __fastcall (__closure *TCaptureOutputEvent)(
@@ -180,7 +184,7 @@ public:
     const UnicodeString & Destination);
     const UnicodeString & Destination);
 
 
   void __fastcall AddOutput(const UnicodeString & Output, bool StdError);
   void __fastcall AddOutput(const UnicodeString & Output, bool StdError);
-  void __fastcall AddExitCode(int ExitCode);
+  void __fastcall ExitCode(int ExitCode);
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TLsSessionAction : public TSessionAction
 class TLsSessionAction : public TSessionAction
@@ -199,6 +203,20 @@ public:
   void __fastcall File(TRemoteFile * File);
   void __fastcall File(TRemoteFile * File);
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+class TChecksumSessionAction : public TFileSessionAction
+{
+public:
+  __fastcall TChecksumSessionAction(TActionLog * Log);
+
+  void __fastcall Checksum(const UnicodeString & Alg, const UnicodeString & Checksum);
+};
+//---------------------------------------------------------------------------
+class TCwdSessionAction : public TSessionAction
+{
+public:
+  __fastcall TCwdSessionAction(TActionLog * Log, const UnicodeString & Path);
+};
+//---------------------------------------------------------------------------
 class TSessionLog : protected TStringList
 class TSessionLog : protected TStringList
 {
 {
 public:
 public:

+ 69 - 36
source/core/SftpFileSystem.cpp

@@ -1854,6 +1854,18 @@ __fastcall TSFTPFileSystem::TSFTPFileSystem(TTerminal * ATerminal,
   FExtensions = new TStringList();
   FExtensions = new TStringList();
   FFixedPaths = NULL;
   FFixedPaths = NULL;
   FFileSystemInfoValid = false;
   FFileSystemInfoValid = false;
+
+  FChecksumAlgs.reset(new TStringList());
+  FChecksumSftpAlgs.reset(new TStringList());
+  // List as defined by draft-ietf-secsh-filexfer-extensions-00
+  // MD5 moved to the back
+  RegisterChecksumAlg(Sha1ChecksumAlg, L"sha1");
+  RegisterChecksumAlg(Sha224ChecksumAlg, L"sha224");
+  RegisterChecksumAlg(Sha256ChecksumAlg, L"sha256");
+  RegisterChecksumAlg(Sha384ChecksumAlg, L"sha384");
+  RegisterChecksumAlg(Sha512ChecksumAlg, L"sha512");
+  RegisterChecksumAlg(Md5ChecksumAlg, L"md5");
+  RegisterChecksumAlg(Crc32ChecksumAlg, L"crc32");
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TSFTPFileSystem::~TSFTPFileSystem()
 __fastcall TSFTPFileSystem::~TSFTPFileSystem()
@@ -3632,6 +3644,11 @@ void __fastcall TSFTPFileSystem::CreateDirectory(const UnicodeString DirName)
 void __fastcall TSFTPFileSystem::CreateLink(const UnicodeString FileName,
 void __fastcall TSFTPFileSystem::CreateLink(const UnicodeString FileName,
   const UnicodeString PointTo, bool Symbolic)
   const UnicodeString PointTo, bool Symbolic)
 {
 {
+  // Cerberus server does not even response to LINK or SYMLINK,
+  // Although its log says:
+  // Unrecognized SFTP client command: (20)
+  // Unknown SFTP packet - Sending Unsupported OP response
+
   assert(FVersion >= 3); // links are supported with SFTP version 3 and later
   assert(FVersion >= 3); // links are supported with SFTP version 3 and later
   bool UseLink = (FVersion >= 6);
   bool UseLink = (FVersion >= 6);
   bool UseHardlink = !Symbolic && !UseLink && FSupportsHardlink;
   bool UseHardlink = !Symbolic && !UseLink && FSupportsHardlink;
@@ -3836,7 +3853,8 @@ bool __fastcall TSFTPFileSystem::LoadFilesProperties(TStrings * FileList)
   return Result;
   return Result;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-void __fastcall TSFTPFileSystem::DoCalculateFilesChecksum(const UnicodeString & Alg,
+void __fastcall TSFTPFileSystem::DoCalculateFilesChecksum(
+  const UnicodeString & Alg, const UnicodeString & SftpAlg,
   TStrings * FileList, TStrings * Checksums,
   TStrings * FileList, TStrings * Checksums,
   TCalculatedChecksumEvent OnCalculatedChecksum,
   TCalculatedChecksumEvent OnCalculatedChecksum,
   TFileOperationProgressType * OperationProgress, bool FirstLevel)
   TFileOperationProgressType * OperationProgress, bool FirstLevel)
@@ -3873,7 +3891,7 @@ void __fastcall TSFTPFileSystem::DoCalculateFilesChecksum(const UnicodeString &
 
 
             // do not collect checksums for files in subdirectories,
             // do not collect checksums for files in subdirectories,
             // only send back checksums via callback
             // only send back checksums via callback
-            DoCalculateFilesChecksum(Alg, SubFileList, NULL,
+            DoCalculateFilesChecksum(Alg, SftpAlg, SubFileList, NULL,
               OnCalculatedChecksum, OperationProgress, false);
               OnCalculatedChecksum, OperationProgress, false);
 
 
             Success = true;
             Success = true;
@@ -3897,43 +3915,54 @@ void __fastcall TSFTPFileSystem::DoCalculateFilesChecksum(const UnicodeString &
   TSFTPCalculateFilesChecksumQueue Queue(this);
   TSFTPCalculateFilesChecksumQueue Queue(this);
   try
   try
   {
   {
-    if (Queue.Init(CalculateFilesChecksumQueueLen, Alg, FileList))
+    if (Queue.Init(CalculateFilesChecksumQueueLen, SftpAlg, FileList))
     {
     {
       TSFTPPacket Packet;
       TSFTPPacket Packet;
       bool Next;
       bool Next;
       do
       do
       {
       {
         bool Success = false;
         bool Success = false;
-        UnicodeString Alg;
         UnicodeString Checksum;
         UnicodeString Checksum;
         TRemoteFile * File = NULL;
         TRemoteFile * File = NULL;
 
 
         try
         try
         {
         {
+          TChecksumSessionAction Action(FTerminal->ActionLog);
           try
           try
           {
           {
             Next = Queue.ReceivePacket(&Packet, File);
             Next = Queue.ReceivePacket(&Packet, File);
             assert(Packet.Type == SSH_FXP_EXTENDED_REPLY);
             assert(Packet.Type == SSH_FXP_EXTENDED_REPLY);
 
 
             OperationProgress->SetFile(File->FileName);
             OperationProgress->SetFile(File->FileName);
+            Action.FileName(FTerminal->AbsolutePath(File->FullFileName, true));
 
 
-            Alg = Packet.GetAnsiString();
-            Checksum = BytesToHex(reinterpret_cast<const unsigned char*>(Packet.GetNextData(Packet.RemainingLength)), Packet.RemainingLength);
-            OnCalculatedChecksum(File->FileName, Alg, Checksum);
+            // skip alg
+            Packet.GetAnsiString();
+            Checksum = BytesToHex(reinterpret_cast<const unsigned char*>(Packet.GetNextData(Packet.RemainingLength)), Packet.RemainingLength, false);
+            if (OnCalculatedChecksum != NULL)
+            {
+              OnCalculatedChecksum(File->FileName, Alg, Checksum);
+            }
+            Action.Checksum(Alg, Checksum);
 
 
             Success = true;
             Success = true;
           }
           }
           catch (Exception & E)
           catch (Exception & E)
           {
           {
-            FTerminal->CommandError(&E, FMTLOAD(CHECKSUM_ERROR,
-              (File != NULL ? File->FullFileName : UnicodeString(L""))));
+            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);
             // TODO: retries? resume?
             // TODO: retries? resume?
             Next = false;
             Next = false;
           }
           }
 
 
           if (Checksums != NULL)
           if (Checksums != NULL)
           {
           {
-            Checksums->Add(L"");
+            Checksums->Add(Checksum);
           }
           }
         }
         }
         __finally
         __finally
@@ -3966,11 +3995,24 @@ void __fastcall TSFTPFileSystem::CalculateFilesChecksum(const UnicodeString & Al
   TFileOperationProgressType Progress(&FTerminal->DoProgress, &FTerminal->DoFinished);
   TFileOperationProgressType Progress(&FTerminal->DoProgress, &FTerminal->DoFinished);
   Progress.Start(foCalculateChecksum, osRemote, FileList->Count);
   Progress.Start(foCalculateChecksum, osRemote, FileList->Count);
 
 
+  UnicodeString NormalizedAlg = FindIdent(Alg, FChecksumAlgs.get());
+  UnicodeString SftpAlg;
+  int Index = FChecksumAlgs->IndexOf(NormalizedAlg);
+  if (Index >= 0)
+  {
+    SftpAlg = FChecksumSftpAlgs->Strings[Index];
+  }
+  else
+  {
+    // try user-specified alg
+    SftpAlg = NormalizedAlg;
+  }
+
   FTerminal->FOperationProgress = &Progress;
   FTerminal->FOperationProgress = &Progress;
 
 
   try
   try
   {
   {
-    DoCalculateFilesChecksum(Alg, FileList, Checksums, OnCalculatedChecksum,
+    DoCalculateFilesChecksum(NormalizedAlg, SftpAlg, FileList, Checksums, OnCalculatedChecksum,
       &Progress, true);
       &Progress, true);
   }
   }
   __finally
   __finally
@@ -4361,13 +4403,12 @@ void __fastcall TSFTPFileSystem::SFTPSourceRobust(const UnicodeString FileName,
   TFileOperationProgressType * OperationProgress, unsigned int Flags)
   TFileOperationProgressType * OperationProgress, unsigned int Flags)
 {
 {
   // the same in TFTPFileSystem
   // the same in TFTPFileSystem
-  bool Retry;
 
 
   TUploadSessionAction Action(FTerminal->ActionLog);
   TUploadSessionAction Action(FTerminal->ActionLog);
+  TRobustOperationLoop RobustLoop(FTerminal, OperationProgress);
 
 
   do
   do
   {
   {
-    Retry = false;
     bool ChildError = false;
     bool ChildError = false;
     try
     try
     {
     {
@@ -4376,9 +4417,7 @@ void __fastcall TSFTPFileSystem::SFTPSourceRobust(const UnicodeString FileName,
     }
     }
     catch(Exception & E)
     catch(Exception & E)
     {
     {
-      Retry = true;
-      if (FTerminal->Active ||
-          !FTerminal->QueryReopen(&E, ropNoReadDirectory, OperationProgress))
+      if (!RobustLoop.TryReopen(E))
       {
       {
         if (!ChildError)
         if (!ChildError)
         {
         {
@@ -4388,7 +4427,7 @@ void __fastcall TSFTPFileSystem::SFTPSourceRobust(const UnicodeString FileName,
       }
       }
     }
     }
 
 
-    if (Retry)
+    if (RobustLoop.ShouldRetry())
     {
     {
       OperationProgress->RollbackTransfer();
       OperationProgress->RollbackTransfer();
       Action.Restart();
       Action.Restart();
@@ -4399,7 +4438,7 @@ void __fastcall TSFTPFileSystem::SFTPSourceRobust(const UnicodeString FileName,
       Flags &= ~tfNewDirectory;
       Flags &= ~tfNewDirectory;
     }
     }
   }
   }
-  while (Retry);
+  while (RobustLoop.Retry());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TSFTPFileSystem::SFTPSource(const UnicodeString FileName,
 void __fastcall TSFTPFileSystem::SFTPSource(const UnicodeString FileName,
@@ -5303,13 +5342,12 @@ void __fastcall TSFTPFileSystem::SFTPSinkRobust(const UnicodeString FileName,
   TFileOperationProgressType * OperationProgress, unsigned int Flags)
   TFileOperationProgressType * OperationProgress, unsigned int Flags)
 {
 {
   // the same in TFTPFileSystem
   // the same in TFTPFileSystem
-  bool Retry;
 
 
   TDownloadSessionAction Action(FTerminal->ActionLog);
   TDownloadSessionAction Action(FTerminal->ActionLog);
+  TRobustOperationLoop RobustLoop(FTerminal, OperationProgress);
 
 
   do
   do
   {
   {
-    Retry = false;
     bool ChildError = false;
     bool ChildError = false;
     try
     try
     {
     {
@@ -5318,9 +5356,7 @@ void __fastcall TSFTPFileSystem::SFTPSinkRobust(const UnicodeString FileName,
     }
     }
     catch(Exception & E)
     catch(Exception & E)
     {
     {
-      Retry = true;
-      if (FTerminal->Active ||
-          !FTerminal->QueryReopen(&E, ropNoReadDirectory, OperationProgress))
+      if (!RobustLoop.TryReopen(E))
       {
       {
         if (!ChildError)
         if (!ChildError)
         {
         {
@@ -5330,7 +5366,7 @@ void __fastcall TSFTPFileSystem::SFTPSinkRobust(const UnicodeString FileName,
       }
       }
     }
     }
 
 
-    if (Retry)
+    if (RobustLoop.ShouldRetry())
     {
     {
       OperationProgress->RollbackTransfer();
       OperationProgress->RollbackTransfer();
       Action.Restart();
       Action.Restart();
@@ -5342,7 +5378,7 @@ void __fastcall TSFTPFileSystem::SFTPSinkRobust(const UnicodeString FileName,
       }
       }
     }
     }
   }
   }
-  while (Retry);
+  while (RobustLoop.Retry());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TSFTPFileSystem::SFTPSink(const UnicodeString FileName,
 void __fastcall TSFTPFileSystem::SFTPSink(const UnicodeString FileName,
@@ -5419,8 +5455,7 @@ void __fastcall TSFTPFileSystem::SFTPSink(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
@@ -5917,15 +5952,13 @@ void __fastcall TSFTPFileSystem::SFTPSinkFile(UnicodeString FileName,
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TSFTPFileSystem::RegisterChecksumAlg(const UnicodeString & Alg, const UnicodeString & SftpAlg)
+{
+  FChecksumAlgs->Add(Alg);
+  FChecksumSftpAlgs->Add(SftpAlg);
+}
+//---------------------------------------------------------------------------
 void __fastcall TSFTPFileSystem::GetSupportedChecksumAlgs(TStrings * Algs)
 void __fastcall TSFTPFileSystem::GetSupportedChecksumAlgs(TStrings * Algs)
 {
 {
-  // List as defined by draft-ietf-secsh-filexfer-extensions-00
-  // MD5 moved to the back
-  Algs->Add(L"sha1");
-  Algs->Add(L"sha224");
-  Algs->Add(L"sha256");
-  Algs->Add(L"sha384");
-  Algs->Add(L"sha512");
-  Algs->Add(L"md5");
-  Algs->Add(L"crc32");
+  Algs->AddStrings(FChecksumAlgs.get());
 }
 }

+ 5 - 1
source/core/SftpFileSystem.h

@@ -107,6 +107,8 @@ protected:
   unsigned long FMaxPacketSize;
   unsigned long FMaxPacketSize;
   bool FSupportsStatVfsV2;
   bool FSupportsStatVfsV2;
   bool FSupportsHardlink;
   bool FSupportsHardlink;
+  std::unique_ptr<TStringList> FChecksumAlgs;
+  std::unique_ptr<TStringList> FChecksumSftpAlgs;
 
 
   void __fastcall SendCustomReadFile(TSFTPPacket * Packet, TSFTPPacket * Response,
   void __fastcall SendCustomReadFile(TSFTPPacket * Packet, TSFTPPacket * Response,
     unsigned long Flags);
     unsigned long Flags);
@@ -141,10 +143,12 @@ protected:
   void __fastcall TryOpenDirectory(const UnicodeString Directory);
   void __fastcall TryOpenDirectory(const UnicodeString Directory);
   bool __fastcall SupportsExtension(const UnicodeString & Extension) const;
   bool __fastcall SupportsExtension(const UnicodeString & Extension) const;
   void __fastcall ResetConnection();
   void __fastcall ResetConnection();
-  void __fastcall DoCalculateFilesChecksum(const UnicodeString & Alg,
+  void __fastcall DoCalculateFilesChecksum(
+    const UnicodeString & Alg, const UnicodeString & SftpAlg,
     TStrings * FileList, TStrings * Checksums,
     TStrings * FileList, TStrings * Checksums,
     TCalculatedChecksumEvent OnCalculatedChecksum,
     TCalculatedChecksumEvent OnCalculatedChecksum,
     TFileOperationProgressType * OperationProgress, bool FirstLevel);
     TFileOperationProgressType * OperationProgress, bool FirstLevel);
+  void __fastcall RegisterChecksumAlg(const UnicodeString & Alg, const UnicodeString & SftpAlg);
   void __fastcall DoDeleteFile(const UnicodeString FileName, unsigned char Type);
   void __fastcall DoDeleteFile(const UnicodeString FileName, unsigned char Type);
 
 
   void __fastcall SFTPSourceRobust(const UnicodeString FileName,
   void __fastcall SFTPSourceRobust(const UnicodeString FileName,

+ 54 - 11
source/core/Terminal.cpp

@@ -76,8 +76,7 @@ private:
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TLoopDetector::TLoopDetector()
 __fastcall TLoopDetector::TLoopDetector()
 {
 {
-  FVisitedDirectories.reset(new TStringList());
-  FVisitedDirectories->Sorted = true;
+  FVisitedDirectories.reset(CreateSortedStringList());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TLoopDetector::RecordVisitedDirectory(const UnicodeString & Directory)
 void __fastcall TLoopDetector::RecordVisitedDirectory(const UnicodeString & Directory)
@@ -551,6 +550,34 @@ void __fastcall TCallbackGuard::Verify()
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+TRobustOperationLoop::TRobustOperationLoop(TTerminal * Terminal, TFileOperationProgressType * OperationProgress) :
+  FTerminal(Terminal),
+  FOperationProgress(OperationProgress),
+  FRetry(false)
+{
+}
+//---------------------------------------------------------------------------
+bool TRobustOperationLoop::TryReopen(Exception & E)
+{
+  FRetry =
+    !FTerminal->Active &&
+    FTerminal->QueryReopen(&E, ropNoReadDirectory, FOperationProgress);
+  return FRetry;
+}
+//---------------------------------------------------------------------------
+bool TRobustOperationLoop::ShouldRetry()
+{
+  return FRetry;
+}
+//---------------------------------------------------------------------------
+bool TRobustOperationLoop::Retry()
+{
+  bool Result = FRetry;
+  FRetry = false;
+  return Result;
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 __fastcall TTerminal::TTerminal(TSessionData * SessionData,
 __fastcall TTerminal::TTerminal(TSessionData * SessionData,
   TConfiguration * Configuration)
   TConfiguration * Configuration)
 {
 {
@@ -2603,7 +2630,25 @@ void __fastcall TTerminal::CustomReadDirectory(TRemoteFileList * FileList)
 {
 {
   assert(FileList);
   assert(FileList);
   assert(FFileSystem);
   assert(FFileSystem);
-  FFileSystem->ReadDirectory(FileList);
+
+  TRobustOperationLoop RobustLoop(this, OperationProgress);
+
+  do
+  {
+    try
+    {
+      FFileSystem->ReadDirectory(FileList);
+    }
+    catch (Exception & E)
+    {
+      if (!RobustLoop.TryReopen(E))
+      {
+        throw;
+      }
+    }
+   }
+  while (RobustLoop.Retry());
+
 
 
   if (Log->Logging)
   if (Log->Logging)
   {
   {
@@ -3398,10 +3443,6 @@ void __fastcall TTerminal::CalculateFileSize(UnicodeString FileName,
           DoCalculateDirectorySize(File->FullFileName, File, AParams);
           DoCalculateDirectorySize(File->FullFileName, File, AParams);
         }
         }
       }
       }
-      else
-      {
-        AParams->Size += File->Size;
-      }
 
 
       if (AParams->Stats != NULL)
       if (AParams->Stats != NULL)
       {
       {
@@ -3449,6 +3490,10 @@ bool __fastcall TTerminal::CalculateFilesSize(TStrings * FileList,
   __int64 & Size, int Params, const TCopyParamType * CopyParam,
   __int64 & Size, int Params, const TCopyParamType * CopyParam,
   bool AllowDirs, TCalculateSizeStats * Stats)
   bool AllowDirs, TCalculateSizeStats * Stats)
 {
 {
+  // With FTP protocol, we may se DSIZ command from
+  // draft-peterson-streamlined-ftp-command-extensions-10
+  // Implemented by Serv-U FTP.
+
   TCalculateSizeParams Param;
   TCalculateSizeParams Param;
   Param.Size = 0;
   Param.Size = 0;
   Param.Params = Params;
   Param.Params = Params;
@@ -3908,7 +3953,7 @@ void __fastcall TTerminal::AnyCommand(const UnicodeString Command,
           FAction.AddOutput(Str, true);
           FAction.AddOutput(Str, true);
           break;
           break;
         case cotExitCode:
         case cotExitCode:
-          FAction.AddExitCode(StrToInt(Str));
+          FAction.ExitCode(StrToInt(Str));
           break;
           break;
       }
       }
 
 
@@ -4455,9 +4500,7 @@ void __fastcall TTerminal::DoSynchronizeCollectDirectory(const UnicodeString Loc
   {
   {
     bool Found;
     bool Found;
     TSearchRecChecked SearchRec;
     TSearchRecChecked SearchRec;
-    Data.LocalFileList = new TStringList();
-    Data.LocalFileList->Sorted = true;
-    Data.LocalFileList->CaseSensitive = false;
+    Data.LocalFileList = CreateSortedStringList();
 
 
     FILE_OPERATION_LOOP_BEGIN
     FILE_OPERATION_LOOP_BEGIN
     {
     {

+ 14 - 0
source/core/Terminal.h

@@ -697,4 +697,18 @@ struct TSpaceAvailable
   unsigned long BytesPerAllocationUnit;
   unsigned long BytesPerAllocationUnit;
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+class TRobustOperationLoop
+{
+public:
+  TRobustOperationLoop(TTerminal * Terminal, TFileOperationProgressType * OperationProgress);
+  bool TryReopen(Exception & E);
+  bool ShouldRetry();
+  bool Retry();
+
+private:
+  TTerminal * FTerminal;
+  TFileOperationProgressType * FOperationProgress;
+  bool FRetry;
+};
+//---------------------------------------------------------------------------
 #endif
 #endif

+ 40 - 18
source/core/WebDAVFileSystem.cpp

@@ -297,8 +297,7 @@ void TWebDAVFileSystem::OpenUrl(const UnicodeString & Url)
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void TWebDAVFileSystem::NeonClientOpenSessionInternal(UnicodeString & CorrectedUrl, UnicodeString Url)
 void TWebDAVFileSystem::NeonClientOpenSessionInternal(UnicodeString & CorrectedUrl, UnicodeString Url)
 {
 {
-  std::unique_ptr<TStringList> AttemptedUrls(new TStringList());
-  AttemptedUrls->Sorted = true;
+  std::unique_ptr<TStringList> AttemptedUrls(CreateSortedStringList());
   int AttemptsLeft = MAX_REDIRECT_ATTEMPTS;
   int AttemptsLeft = MAX_REDIRECT_ATTEMPTS;
   while (true)
   while (true)
   {
   {
@@ -516,6 +515,21 @@ void __fastcall TWebDAVFileSystem::CollectUsage()
   {
   {
     FTerminal->CollectTlsUsage(FTlsVersionStr);
     FTerminal->CollectTlsUsage(FTlsVersionStr);
   }
   }
+
+  UnicodeString RemoteSystem = FFileSystemInfo.RemoteSystem;
+  if (ContainsText(RemoteSystem, L"Microsoft-IIS"))
+  {
+    FTerminal->Configuration->Usage->Inc(L"OpenedSessionsWebDAVIIS");
+  }
+  else if (ContainsText(RemoteSystem, L"IT Hit WebDAV Server"))
+  {
+    FTerminal->Configuration->Usage->Inc(L"OpenedSessionsWebDAVITHit");
+  }
+  else
+  {
+    // Web also know OpenDrive, Yandex, iFiles (iOS), Swapper (iOS), SafeSync
+    FTerminal->Configuration->Usage->Inc(L"OpenedSessionsWebDAVOther");
+  }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 const TSessionInfo & __fastcall TWebDAVFileSystem::GetSessionInfo()
 const TSessionInfo & __fastcall TWebDAVFileSystem::GetSessionInfo()
@@ -757,8 +771,7 @@ void __fastcall TWebDAVFileSystem::ChangeDirectory(const UnicodeString ADirector
 {
 {
   UnicodeString Path = AbsolutePath(ADirectory, false);
   UnicodeString Path = AbsolutePath(ADirectory, false);
 
 
-  // to verify existence of directory try to open it (SSH_FXP_REALPATH succeeds
-  // for invalid paths on some systems, like CygWin)
+  // to verify existence of directory try to open it
   TryOpenDirectory(Path);
   TryOpenDirectory(Path);
 
 
   // if open dir did not fail, directory exists -> success.
   // if open dir did not fail, directory exists -> success.
@@ -1235,13 +1248,12 @@ void __fastcall TWebDAVFileSystem::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;
     bool ChildError = false;
     bool ChildError = false;
     try
     try
     {
     {
@@ -1250,9 +1262,7 @@ void __fastcall TWebDAVFileSystem::SourceRobust(const UnicodeString FileName,
     }
     }
     catch (Exception & E)
     catch (Exception & E)
     {
     {
-      Retry = true;
-      if (FTerminal->Active ||
-          !FTerminal->QueryReopen(&E, ropNoReadDirectory, OperationProgress))
+      if (!RobustLoop.TryReopen(E))
       {
       {
         if (!ChildError)
         if (!ChildError)
         {
         {
@@ -1262,7 +1272,7 @@ void __fastcall TWebDAVFileSystem::SourceRobust(const UnicodeString FileName,
       }
       }
     }
     }
 
 
-    if (Retry)
+    if (RobustLoop.ShouldRetry())
     {
     {
       OperationProgress->RollbackTransfer();
       OperationProgress->RollbackTransfer();
       Action.Restart();
       Action.Restart();
@@ -1271,7 +1281,7 @@ void __fastcall TWebDAVFileSystem::SourceRobust(const UnicodeString FileName,
       Params |= cpNoConfirmation;
       Params |= cpNoConfirmation;
     }
     }
   }
   }
-  while (Retry);
+  while (RobustLoop.Retry());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TWebDAVFileSystem::Source(const UnicodeString FileName,
 void __fastcall TWebDAVFileSystem::Source(const UnicodeString FileName,
@@ -1603,13 +1613,12 @@ void __fastcall TWebDAVFileSystem::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;
     bool ChildError = false;
     bool ChildError = false;
     try
     try
     {
     {
@@ -1618,9 +1627,7 @@ void __fastcall TWebDAVFileSystem::SinkRobust(const UnicodeString FileName,
     }
     }
     catch (Exception & E)
     catch (Exception & E)
     {
     {
-      Retry = true;
-      if (FTerminal->GetActive() ||
-          !FTerminal->QueryReopen(&E, ropNoReadDirectory, OperationProgress))
+      if (!RobustLoop.TryReopen(E))
       {
       {
         if (!ChildError)
         if (!ChildError)
         {
         {
@@ -1630,7 +1637,7 @@ void __fastcall TWebDAVFileSystem::SinkRobust(const UnicodeString FileName,
       }
       }
     }
     }
 
 
-    if (Retry)
+    if (RobustLoop.ShouldRetry())
     {
     {
       OperationProgress->RollbackTransfer();
       OperationProgress->RollbackTransfer();
       Action.Restart();
       Action.Restart();
@@ -1642,7 +1649,7 @@ void __fastcall TWebDAVFileSystem::SinkRobust(const UnicodeString FileName,
       }
       }
     }
     }
   }
   }
-  while (Retry);
+  while (RobustLoop.Retry());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void TWebDAVFileSystem::NeonCreateRequest(
 void TWebDAVFileSystem::NeonCreateRequest(
@@ -1658,6 +1665,21 @@ void TWebDAVFileSystem::NeonPreSend(
 {
 {
   TWebDAVFileSystem * FileSystem = static_cast<TWebDAVFileSystem *>(UserData);
   TWebDAVFileSystem * FileSystem = static_cast<TWebDAVFileSystem *>(UserData);
 
 
+  if (FileSystem->FDownloading)
+  {
+    // Needed by IIS server to make it download source code, not code output,
+    // and mainly to even allow downloading file with unregistered extensions.
+    // Without it files like .001 return 404 (Not found) HTTP code.
+    // http://msdn.microsoft.com/en-us/library/cc250098.aspx
+    // http://msdn.microsoft.com/en-us/library/cc250216.aspx
+    // http://lists.manyfish.co.uk/pipermail/neon/2012-April/001452.html
+    // It's also supported by Oracle server:
+    // https://docs.oracle.com/cd/E19146-01/821-1828/gczya/index.html
+    // We do not know yet of ay server that fails when the header is used,
+    // so it's added unconditionally.
+    ne_buffer_zappend(Header, "Translate: f\r\n");
+  }
+
   if (FileSystem->FTerminal->Log->Logging)
   if (FileSystem->FTerminal->Log->Logging)
   {
   {
     const char * Buffer;
     const char * Buffer;

+ 3 - 0
source/filezilla/AsyncSocketEx.cpp

@@ -1385,7 +1385,10 @@ BOOL CAsyncSocketEx::Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen)
 		res = SOCKET_ERROR!=m_pFirstLayer->Connect(lpSockAddr, nSockAddrLen);
 		res = SOCKET_ERROR!=m_pFirstLayer->Connect(lpSockAddr, nSockAddrLen);
 	else
 	else
 #endif //NOLAYERS
 #endif //NOLAYERS
+	{
+		ConfigureSocket();
 		res = SOCKET_ERROR!=connect(m_SocketData.hSocket, lpSockAddr, nSockAddrLen);
 		res = SOCKET_ERROR!=connect(m_SocketData.hSocket, lpSockAddr, nSockAddrLen);
+	}
 
 
 #ifndef NOSOCKETSTATES
 #ifndef NOSOCKETSTATES
 	if (res || GetLastError()==WSAEWOULDBLOCK)
 	if (res || GetLastError()==WSAEWOULDBLOCK)

+ 1 - 0
source/filezilla/AsyncSocketEx.h

@@ -321,6 +321,7 @@ protected:
 
 
 	virtual void LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat) {};
 	virtual void LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat) {};
 	virtual bool LoggingSocketMessage(int nMessageType) { return true; };
 	virtual bool LoggingSocketMessage(int nMessageType) { return true; };
+	virtual void ConfigureSocket() {};
 };
 };
 
 
 #ifndef NOLAYERS
 #ifndef NOLAYERS

+ 5 - 2
source/filezilla/FileZillaIntf.cpp

@@ -135,9 +135,12 @@ bool __fastcall TFileZillaIntf::Close(bool AllowBusy)
 
 
   switch (ReturnCode)
   switch (ReturnCode)
   {
   {
-    // it the connection terminated itself meanwhile
+    // If the connection terminated itself meanwhile,
+    // do not try to wait for close response.
     case FZ_REPLY_NOTCONNECTED:
     case FZ_REPLY_NOTCONNECTED:
-      Result = true;
+      // We might check AllowBusy here, as it's actually similar scenario,
+      // as we expect this to happen during authentication only
+      Result = false;
       break;
       break;
 
 
     // waiting for disconnect
     // waiting for disconnect

+ 2 - 0
source/filezilla/FileZillaOpt.h

@@ -167,5 +167,7 @@
 #define OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY 1006
 #define OPTION_MPEXT_TRANSFER_ACTIVE_IMMEDIATELY 1006
 #define OPTION_MPEXT_REMOVE_BOM 1007
 #define OPTION_MPEXT_REMOVE_BOM 1007
 #define OPTION_MPEXT_LOG_SENSITIVE 1008
 #define OPTION_MPEXT_LOG_SENSITIVE 1008
+#define OPTION_MPEXT_HOST 1009
+#define OPTION_MPEXT_NODELAY 1010
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #endif // FileZillaOptH
 #endif // FileZillaOptH

+ 80 - 26
source/filezilla/FtpControlSocket.cpp

@@ -26,7 +26,6 @@
 #ifndef MPEXT
 #ifndef MPEXT
 #include "fileexistsdlg.h"
 #include "fileexistsdlg.h"
 #endif
 #endif
-#include "pathfunctions.h"
 #include "asyncproxysocketlayer.h"
 #include "asyncproxysocketlayer.h"
 #ifndef MPEXT_NO_SSL
 #ifndef MPEXT_NO_SSL
 #include "AsyncSslSocketLayer.h"
 #include "AsyncSslSocketLayer.h"
@@ -259,6 +258,9 @@ CFtpControlSocket::~CFtpControlSocket()
 #define CONNECT_CLNT -17
 #define CONNECT_CLNT -17
 #define CONNECT_OPTSMLST -18
 #define CONNECT_OPTSMLST -18
 #define CONNECT_NEEDPASS -19
 #define CONNECT_NEEDPASS -19
+#ifdef MPEXT
+#define CONNECT_HOST -20
+#endif
 
 
 bool CFtpControlSocket::InitConnect()
 bool CFtpControlSocket::InitConnect()
 {
 {
@@ -402,6 +404,21 @@ bool CFtpControlSocket::InitConnect()
 	return true;
 	return true;
 }
 }
 
 
+int CFtpControlSocket::InitConnectState()
+{
+#ifndef MPEXT_NO_SSL
+	if ((m_CurrentServer.nServerType & FZ_SERVERTYPE_LAYERMASK) & (FZ_SERVERTYPE_LAYER_SSL_EXPLICIT | FZ_SERVERTYPE_LAYER_TLS_EXPLICIT))
+		return CONNECT_SSL_INIT;
+	else
+#endif
+#ifndef MPEXT_NO_GSS
+	if (m_pGssLayer)
+		return CONNECT_GSS_INIT;
+	else
+#endif
+	return CONNECT_INIT;
+}
+
 void CFtpControlSocket::Connect(t_server &server)
 void CFtpControlSocket::Connect(t_server &server)
 {
 {
 	USES_CONVERSION;
 	USES_CONVERSION;
@@ -427,11 +444,14 @@ void CFtpControlSocket::Connect(t_server &server)
 	}
 	}
 	AsyncSelect();
 	AsyncSelect();
 
 
-	m_Operation.nOpState =
-#ifndef MPEXT_NO_GSS
-	m_pGssLayer ? CONNECT_GSS_INIT :
-#endif
-	CONNECT_INIT;
+	if (COptions::GetOptionVal(OPTION_MPEXT_HOST))
+	{
+		m_Operation.nOpState = CONNECT_HOST;
+	}
+	else
+	{
+		m_Operation.nOpState = InitConnectState();
+	}
 
 
 #ifndef MPEXT_NO_SSL
 #ifndef MPEXT_NO_SSL
 	if (server.nServerType & FZ_SERVERTYPE_LAYER_SSL_IMPLICIT)
 	if (server.nServerType & FZ_SERVERTYPE_LAYER_SSL_IMPLICIT)
@@ -489,11 +509,6 @@ void CFtpControlSocket::Connect(t_server &server)
 	str.Format(IDS_STATUSMSG_CONNECTING, hostname);
 	str.Format(IDS_STATUSMSG_CONNECTING, hostname);
 	ShowStatus(str, FZ_LOG_STATUS);
 	ShowStatus(str, FZ_LOG_STATUS);
 
 
-#ifndef MPEXT_NO_SSL
-	if ((server.nServerType & FZ_SERVERTYPE_LAYERMASK) & (FZ_SERVERTYPE_LAYER_SSL_EXPLICIT | FZ_SERVERTYPE_LAYER_TLS_EXPLICIT))
-		m_Operation.nOpState = CONNECT_SSL_INIT;
-#endif
-
 	if (!CControlSocket::Connect(temp, port))
 	if (!CControlSocket::Connect(temp, port))
 	{
 	{
 		if (WSAGetLastError() != WSAEWOULDBLOCK)
 		if (WSAGetLastError() != WSAEWOULDBLOCK)
@@ -635,6 +650,16 @@ void CFtpControlSocket::LogOnToServer(BOOL bSkipReply /*=FALSE*/)
 			return;
 			return;
 		}
 		}
 	}
 	}
+#endif
+#ifdef MPEXT
+	else if (m_Operation.nOpState == CONNECT_HOST)
+	{
+		if (Send(_MPT("HOST " + m_CurrentServer.host)))
+		{
+			m_Operation.nOpState = InitConnectState();
+			return;
+		}
+	}
 #endif
 #endif
 	else if (m_Operation.nOpState == CONNECT_OPTSMLST)
 	else if (m_Operation.nOpState == CONNECT_OPTSMLST)
 	{
 	{
@@ -3614,10 +3639,7 @@ void CFtpControlSocket::FileTransfer(t_transferfile *transferfile/*=0*/,BOOL bFi
 			}
 			}
 			else if (code==4 || code==5) //LIST failed, try getting file information using SIZE and MDTM
 			else if (code==4 || code==5) //LIST failed, try getting file information using SIZE and MDTM
 			{
 			{
-				if (m_pTransferSocket)
-					delete m_pTransferSocket;
-				m_pTransferSocket=0;
-				m_Operation.nOpState = FILETRANSFER_NOLIST_SIZE;
+				TransferHandleListError();
 			}
 			}
 			else if (code!=1)
 			else if (code!=1)
 				nReplyError=FZ_REPLY_ERROR;
 				nReplyError=FZ_REPLY_ERROR;
@@ -3628,9 +3650,18 @@ void CFtpControlSocket::FileTransfer(t_transferfile *transferfile/*=0*/,BOOL bFi
 			if (!bFinish)
 			if (!bFinish)
 			{
 			{
 				if (code!=2 && code!=3)
 				if (code!=2 && code!=3)
-					nReplyError=FZ_REPLY_ERROR;
+				{
+					if (code==4 || code==5)
+					{
+						TransferHandleListError();
+					}
+					else
+					{
+						nReplyError=FZ_REPLY_ERROR;
+					}
+				}
 				else
 				else
-					pData->nGotTransferEndReply = 1;
+				pData->nGotTransferEndReply = 1;
 			}
 			}
 			if (pData->nGotTransferEndReply && pData->pDirectoryListing)
 			if (pData->nGotTransferEndReply && pData->pDirectoryListing)
 			{
 			{
@@ -4636,6 +4667,14 @@ void CFtpControlSocket::FileTransfer(t_transferfile *transferfile/*=0*/,BOOL bFi
 	}
 	}
 }
 }
 
 
+void CFtpControlSocket::TransferHandleListError()
+{
+	if (m_pTransferSocket)
+		delete m_pTransferSocket;
+	m_pTransferSocket=0;
+	m_Operation.nOpState = FILETRANSFER_NOLIST_SIZE;
+}
+
 bool CFtpControlSocket::HandleMdtm(int code, t_directory::t_direntry::t_date & date)
 bool CFtpControlSocket::HandleMdtm(int code, t_directory::t_direntry::t_date & date)
 {
 {
 	bool result = false;
 	bool result = false;
@@ -5282,7 +5321,9 @@ public:
 int CFtpControlSocket::CheckOverwriteFile()
 int CFtpControlSocket::CheckOverwriteFile()
 {
 {
 	if (!m_Operation.pData)
 	if (!m_Operation.pData)
+	{
 		return FZ_REPLY_ERROR;
 		return FZ_REPLY_ERROR;
+	}
 
 
 	CFileTransferData *pData = reinterpret_cast<CFileTransferData *>(m_Operation.pData);
 	CFileTransferData *pData = reinterpret_cast<CFileTransferData *>(m_Operation.pData);
 
 
@@ -5290,14 +5331,23 @@ int CFtpControlSocket::CheckOverwriteFile()
 	CFileStatus64 status;
 	CFileStatus64 status;
 	BOOL res = GetStatus64(pData->transferfile.localfile, status);
 	BOOL res = GetStatus64(pData->transferfile.localfile, status);
 	if (!res)
 	if (!res)
+	{
 		if (!pData->transferfile.get)
 		if (!pData->transferfile.get)
+		{
+			ShowStatus(IDS_ERRORMSG_CANTGETLISTFILE,FZ_LOG_ERROR);
 			nReplyError = FZ_REPLY_CRITICALERROR; //File has to exist when uploading
 			nReplyError = FZ_REPLY_CRITICALERROR; //File has to exist when uploading
+		}
 		else
 		else
+		{
 			m_Operation.nOpState = FILETRANSFER_TYPE;
 			m_Operation.nOpState = FILETRANSFER_TYPE;
+		}
+	}
 	else
 	else
 	{
 	{
 		if (status.m_attribute & 0x10)
 		if (status.m_attribute & 0x10)
+		{
 			nReplyError = FZ_REPLY_CRITICALERROR; //Can't transfer to/from dirs
 			nReplyError = FZ_REPLY_CRITICALERROR; //Can't transfer to/from dirs
+		}
 		else
 		else
 		{
 		{
 			_int64 localsize;
 			_int64 localsize;
@@ -6275,29 +6325,33 @@ void CFtpControlSocket::DiscardLine(CStringA line)
 	if (m_Operation.nOpMode == CSMODE_CONNECT && m_Operation.nOpState == CONNECT_FEAT)
 	if (m_Operation.nOpMode == CSMODE_CONNECT && m_Operation.nOpState == CONNECT_FEAT)
 	{
 	{
 		line.MakeUpper();
 		line.MakeUpper();
+		while (line.Left(1) == " ")
+		{
+			line = line.Mid(1, line.GetLength() - 1);
+		}
 #ifndef MPEXT_NO_ZLIB
 #ifndef MPEXT_NO_ZLIB
-		if (line == _MPAT(" MODE Z") || line.Left(8) == _MPAT(" MODE Z "))
+		if (line == _MPAT("MODE Z") || line.Left(7) == _MPAT("MODE Z "))
 			m_zlibSupported = true;
 			m_zlibSupported = true;
 		else
 		else
 #endif
 #endif
-			if (line == _MPAT(" UTF8") && m_CurrentServer.nUTF8 != 2)
+			if (line == _MPAT("UTF8") && m_CurrentServer.nUTF8 != 2)
 			m_bAnnouncesUTF8 = true;
 			m_bAnnouncesUTF8 = true;
-		else if (line == _MPAT(" CLNT") || line.Left(6) == _MPAT(" CLNT "))
+		else if (line == _MPAT("CLNT") || line.Left(5) == _MPAT("CLNT "))
 			m_hasClntCmd = true;
 			m_hasClntCmd = true;
 #ifdef MPEXT
 #ifdef MPEXT
-		else if (line == _MPAT(" MLSD"))
+		else if (line == _MPAT("MLSD"))
 		{
 		{
 			m_serverCapabilities.SetCapability(mlsd_command, yes);
 			m_serverCapabilities.SetCapability(mlsd_command, yes);
 		}
 		}
-		else if (line == _MPAT(" MDTM"))
+		else if (line == _MPAT("MDTM"))
 		{
 		{
 			m_serverCapabilities.SetCapability(mdtm_command, yes);
 			m_serverCapabilities.SetCapability(mdtm_command, yes);
 		}
 		}
-		else if (line == _MPAT(" SIZE"))
+		else if (line == _MPAT("SIZE"))
 		{
 		{
 			m_serverCapabilities.SetCapability(size_command, yes);
 			m_serverCapabilities.SetCapability(size_command, yes);
 		}
 		}
-		else if (line.Left(5) == _MPAT(" MLST"))
+		else if (line.Left(4) == _MPAT("MLST"))
 		{
 		{
 			USES_CONVERSION;
 			USES_CONVERSION;
 			// This is wrong, the -1 for length does not work with
 			// This is wrong, the -1 for length does not work with
@@ -6307,9 +6361,9 @@ void CFtpControlSocket::DiscardLine(CStringA line)
 			// and OPTS MLST command is never sent.
 			// and OPTS MLST command is never sent.
 			// Also using 6 index when there are no facts after
 			// Also using 6 index when there are no facts after
 			// MLST (ftp.drivehq.com) triggers an assertion.
 			// MLST (ftp.drivehq.com) triggers an assertion.
-			m_serverCapabilities.SetCapability(mlsd_command, yes, (LPCSTR)line.Mid(6, -1));
+			m_serverCapabilities.SetCapability(mlsd_command, yes, (LPCSTR)line.Mid(5, -1));
 		}
 		}
-		else if (line == _MPAT(" MFMT"))
+		else if (line == _MPAT("MFMT"))
 		{
 		{
 			m_serverCapabilities.SetCapability(mfmt_command, yes);
 			m_serverCapabilities.SetCapability(mfmt_command, yes);
 		}
 		}

+ 2 - 0
source/filezilla/FtpControlSocket.h

@@ -93,6 +93,7 @@ public:
 	virtual std::string GetCipherName();
 	virtual std::string GetCipherName();
 	bool HandleSize(int code, __int64 & size);
 	bool HandleSize(int code, __int64 & size);
 	bool HandleMdtm(int code, t_directory::t_direntry::t_date & date);
 	bool HandleMdtm(int code, t_directory::t_direntry::t_date & date);
+	void TransferHandleListError();
 #endif
 #endif
 
 
 	// Vom Klassen-Assistenten generierte virtuelle Funktionsüberschreibungen
 	// Vom Klassen-Assistenten generierte virtuelle Funktionsüberschreibungen
@@ -139,6 +140,7 @@ protected:
 	CString GetListingCmd();
 	CString GetListingCmd();
 
 
 	bool InitConnect();
 	bool InitConnect();
+	int InitConnectState();
 
 
 #ifdef MPEXT
 #ifdef MPEXT
 	bool IsRoutableAddress(const CString & host);
 	bool IsRoutableAddress(const CString & host);

+ 1 - 5
source/filezilla/FtpListResult.cpp

@@ -424,7 +424,6 @@ t_directory::t_direntry *CFtpListResult::getList(int &num, CTime EntryTime)
 	#ifdef _DEBUG
 	#ifdef _DEBUG
 	USES_CONVERSION;
 	USES_CONVERSION;
 	#endif
 	#endif
-	t_directory::t_direntry direntry;
 	char *line=GetLine();
 	char *line=GetLine();
 	m_curline=line;
 	m_curline=line;
 	while (line)
 	while (line)
@@ -432,6 +431,7 @@ t_directory::t_direntry *CFtpListResult::getList(int &num, CTime EntryTime)
 		int tmp;
 		int tmp;
 		char *tmpline = new char[strlen(line) + 1];
 		char *tmpline = new char[strlen(line) + 1];
 		strcpy(tmpline, line);
 		strcpy(tmpline, line);
+		t_directory::t_direntry direntry;
 		if (parseLine(tmpline, strlen(tmpline), direntry, tmp))
 		if (parseLine(tmpline, strlen(tmpline), direntry, tmp))
 		{
 		{
 			delete [] tmpline;
 			delete [] tmpline;
@@ -1306,7 +1306,6 @@ BOOL CFtpListResult::parseAsMlsd(const char *line, const int linelen, t_director
 	direntry.bUnsure = FALSE;
 	direntry.bUnsure = FALSE;
 	direntry.dir = FALSE;
 	direntry.dir = FALSE;
 	direntry.bLink = FALSE;
 	direntry.bLink = FALSE;
-	direntry.size = -1;
 	direntry.ownergroup = _T("");
 	direntry.ownergroup = _T("");
 	direntry.permissionstr = _T("");
 	direntry.permissionstr = _T("");
 
 
@@ -2126,7 +2125,6 @@ BOOL CFtpListResult::parseAsDos(const char *line, const int linelen, t_directory
 	if (tokenlen == 5 && !memcmp(str, "<DIR>", 5))
 	if (tokenlen == 5 && !memcmp(str, "<DIR>", 5))
 	{
 	{
 		direntry.dir = TRUE;
 		direntry.dir = TRUE;
-		direntry.size = -1;
 	}
 	}
 	else
 	else
 	{
 	{
@@ -2673,7 +2671,6 @@ BOOL CFtpListResult::parseAsIBMMVS(const char *line, const int linelen, t_direct
 	if (tokenlen == 2 && !memcmp(str, "PO", 2))
 	if (tokenlen == 2 && !memcmp(str, "PO", 2))
 	{
 	{
 		direntry.dir = TRUE;
 		direntry.dir = TRUE;
-		direntry.size = -1;
 	}
 	}
 	else
 	else
 	{
 	{
@@ -2777,7 +2774,6 @@ BOOL CFtpListResult::parseAsIBMMVSPDS2(const char *line, const int linelen, t_di
 	direntry.bLink = FALSE;
 	direntry.bLink = FALSE;
 	direntry.ownergroup = _MPT("");
 	direntry.ownergroup = _MPT("");
 	direntry.permissionstr = _MPT("");
 	direntry.permissionstr = _MPT("");
-	direntry.size = -1;
 	direntry.date.hasdate = direntry.date.hastime = FALSE;
 	direntry.date.hasdate = direntry.date.hastime = FALSE;
 
 
 	// pds member name
 	// pds member name

+ 2 - 19
source/filezilla/MFC64bitFix.cpp

@@ -44,21 +44,14 @@ BOOL GetLength64(CString filename, _int64 &size)
 	return TRUE;	
 	return TRUE;	
 }
 }
 
 
-BOOL AFXAPI AfxFullPath(LPTSTR lpszPathOut, LPCTSTR lpszFileIn);
-
 BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64& rStatus)
 BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64& rStatus)
 {
 {
-	// attempt to fully qualify path first
-	if (!AfxFullPath(rStatus.m_szFullName, lpszFileName))
-	{
-		rStatus.m_szFullName[0] = _MPT('\0');
-		return FALSE;
-	}
-
 	WIN32_FIND_DATA findFileData;
 	WIN32_FIND_DATA findFileData;
 	HANDLE hFind = FindFirstFile((LPTSTR)lpszFileName, &findFileData);
 	HANDLE hFind = FindFirstFile((LPTSTR)lpszFileName, &findFileData);
 	if (hFind == INVALID_HANDLE_VALUE)
 	if (hFind == INVALID_HANDLE_VALUE)
+	{
 		return FALSE;
 		return FALSE;
+	}
 	VERIFY(FindClose(hFind));
 	VERIFY(FindClose(hFind));
 
 
 	// strip attribute of NORMAL bit, our API doesn't have a "normal" bit.
 	// strip attribute of NORMAL bit, our API doesn't have a "normal" bit.
@@ -137,13 +130,3 @@ BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64& rStatus)
 
 
 	return TRUE;
 	return TRUE;
 }
 }
-
-_int64 GetPosition64(CFile &file)
-{
-	LONG low=0;
-	LONG high=0;
-	low=SetFilePointer((HANDLE)file.m_hFile, low, &high, FILE_CURRENT);
-	if (low==0xFFFFFFFF && GetLastError!=NO_ERROR)
-		CFileException::ThrowOsError((LONG)::GetLastError());
-	return ((_int64)high<<32)+low;
-}

+ 0 - 3
source/filezilla/MFC64bitFix.h

@@ -32,9 +32,6 @@ struct CFileStatus64
 	_int64 m_size;            // logical size of file in bytes
 	_int64 m_size;            // logical size of file in bytes
 	BYTE m_attribute;       // logical OR of CFile::Attribute enum values
 	BYTE m_attribute;       // logical OR of CFile::Attribute enum values
 	BYTE _m_padding;        // pad the structure to a WORD
 	BYTE _m_padding;        // pad the structure to a WORD
-	TCHAR m_szFullName[_MAX_PATH]; // absolute path name
 };
 };
 
 
 BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64& rStatus);
 BOOL PASCAL GetStatus64(LPCTSTR lpszFileName, CFileStatus64& rStatus);
-
-_int64 GetPosition64(CFile &file);

+ 0 - 98
source/filezilla/PathFunctions.cpp

@@ -1,98 +0,0 @@
-// FileZilla - a Windows ftp client
-
-// Copyright (C) 2002-2004 - Tim Kosse <[email protected]>
-
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-#include "stdafx.h"
-#include "pathfunctions.h"
-
-void PathRemoveArgs(CString &path)
-{
-	path.TrimLeft( _T(" ") );
-	path.TrimRight( _T(" ") );
-	if (path==_MPT(""))
-		return;
-	BOOL quoted=FALSE;
-	if (path[0]==_MPT('\"'))
-		quoted=TRUE;
-	int pos=path.ReverseFind(_MPT('\\'));
-	if (pos==-1)
-		pos=quoted?1:0;
-
-	int i;
-	for (i=pos;i<path.GetLength();i++)
-	{
-		if (path[i]==_MPT('\"'))
-			break;
-		if (path[i]==_MPT(' ') && !quoted)
-			break;
-	}
-	path = path.Left(i+1);
-	path.TrimRight(_MPT(' '));
-}
-
-void PathUnquoteSpaces(CString &path)
-{
-	int pos;
-	while ((pos=path.Find(_MPT('\"')))!=-1)
-		path.SetAt(pos,_MPT(' '));
-	path.TrimLeft( _T(" ") );
-	path.TrimRight( _T(" ") );
-}
-
-CString PathFindExtension(CString path)
-{
-	int pos=path.ReverseFind(_MPT('.'));
-	if (pos==-1)
-		return _MPT("");
-	return path.Mid(pos);
-}
-
-void PathRemoveFileSpec(CString &path)
-{
-	CFileStatus64 status;
-	if (GetStatus64(path,status))
-	{
-		if (status.m_attribute&0x10)
-		{
-			path.TrimRight( _T("\\") );
-			path=path+_MPT("\\");
-			return;
-		}
-		else
-			path.TrimRight( _T("\\") );
-	}
-	if (path.Right(1)!=_MPT("\\"))
-	{
-		path.TrimRight( _T("\\") );
-		int pos=path.ReverseFind(_MPT('\\'));
-		if (pos==-1)
-			path=_MPT("");
-		else
-			path=path.Left(pos+1);
-	}
-}
-
-CString PathAppend(CString path, LPCTSTR sub)
-{
-	ASSERT(sub);
-
-	if (path.Right(1) != _T("\\"))
-		path += _T("\\");
-	path += sub;
-
-	return path;
-}

+ 0 - 28
source/filezilla/PathFunctions.h

@@ -1,28 +0,0 @@
-// FileZilla - a Windows ftp client
-
-// Copyright (C) 2002-2004 - Tim Kosse <[email protected]>
-
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-//Replacement of the Path functions defined in shlwapi.h / shlwapi.dll
-//shlwapi requires IE4, FileZilla should not!
-
-#pragma once
-
-CString PathAppend(CString path, LPCTSTR sub);
-void PathRemoveArgs(CString &path);
-void PathUnquoteSpaces(CString &path);
-CString PathFindExtension(CString path);
-void PathRemoveFileSpec(CString &path);

+ 19 - 1
source/filezilla/TransferSocket.cpp

@@ -427,6 +427,24 @@ void CTransferSocket::OnAccept(int nErrorCode)
 	}
 	}
 }
 }
 
 
+void CTransferSocket::ConfigureSocket()
+{
+	// Note that FileZilla re-enables Nagle's alg during TLS negotiation.
+
+	// Following post claims that TCP_NODELAY
+	// has to be set before connect()
+	// http://stackoverflow.com/questions/22583941/what-is-the-workaround-for-tcp-delayed-acknowledgment/25871250#25871250
+
+	int nodelay = COptions::GetOptionVal(OPTION_MPEXT_NODELAY);
+	if (nodelay != 0)
+	{
+		BOOL bvalue = TRUE;
+		SetSockOpt(TCP_NODELAY, &bvalue, sizeof(bvalue), IPPROTO_TCP);
+	}
+
+	CAsyncSocketEx::ConfigureSocket();
+}
+
 void CTransferSocket::OnConnect(int nErrorCode)
 void CTransferSocket::OnConnect(int nErrorCode)
 {
 {
 	LogMessage(__FILE__, __LINE__, this,FZ_LOG_DEBUG, _T("OnConnect(%d)"), nErrorCode);
 	LogMessage(__FILE__, __LINE__, this,FZ_LOG_DEBUG, _T("OnConnect(%d)"), nErrorCode);
@@ -464,7 +482,7 @@ void CTransferSocket::OnConnect(int nErrorCode)
 		// will call back to OnConnected (as we won't be connected yet).
 		// will call back to OnConnected (as we won't be connected yet).
 		// This is needed for file transfers only, where SetActive is
 		// This is needed for file transfers only, where SetActive is
 		// called only after 1xx response to RETR (and similar) arrives.
 		// called only after 1xx response to RETR (and similar) arrives.
-		// But we get FD_CONNECT earlier, hence we get to thisa branch.
+		// But we get FD_CONNECT earlier, hence we get to this branch.
 		// With directory listing, SetActive is called before Connect,
 		// With directory listing, SetActive is called before Connect,
 		// so we are already STATE_STARTING on FD_CONNECT.
 		// so we are already STATE_STARTING on FD_CONNECT.
 		// It should probably behave the same in both scenarios.
 		// It should probably behave the same in both scenarios.

+ 1 - 0
source/filezilla/TransferSocket.h

@@ -99,6 +99,7 @@ protected:
 	virtual int OnLayerCallback(std::list<t_callbackMsg>& callbacks);
 	virtual int OnLayerCallback(std::list<t_callbackMsg>& callbacks);
 	int ReadDataFromFile(char *buffer, int len);
 	int ReadDataFromFile(char *buffer, int len);
 	virtual void LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat);
 	virtual void LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat);
+	virtual void ConfigureSocket();
 
 
 	CFtpControlSocket *m_pOwner;
 	CFtpControlSocket *m_pOwner;
 	CAsyncProxySocketLayer* m_pProxyLayer;
 	CAsyncProxySocketLayer* m_pProxyLayer;

+ 2 - 0
source/forms/Cleanup.cpp

@@ -135,6 +135,8 @@ void __fastcall TCleanupDialog::InitControls()
     Item->SubItems->Add(Location);
     Item->SubItems->Add(Location);
     assert(Item->Index == i - 1);
     assert(Item->Index == i - 1);
   }
   }
+
+  AutoSizeListColumnsWidth(DataListView);
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
 void __fastcall TCleanupDialog::UpdateControls()
 void __fastcall TCleanupDialog::UpdateControls()

+ 0 - 1
source/forms/Cleanup.dfm

@@ -65,7 +65,6 @@ object CleanupDialog: TCleanupDialog
     Columns = <
     Columns = <
       item
       item
         Caption = 'Data'
         Caption = 'Data'
-        Tag = 1
         Width = 160
         Width = 160
       end
       end
       item
       item

+ 21 - 1
source/forms/Copy.cpp

@@ -76,6 +76,7 @@ __fastcall TCopyDialog::TCopyDialog(
 
 
   HotTrackLabel(CopyParamLabel);
   HotTrackLabel(CopyParamLabel);
   CopyParamListButton(TransferSettingsButton);
   CopyParamListButton(TransferSettingsButton);
+  HotTrackLabel(ShortCutHintLabel);
 
 
   UseSystemSettings(this);
   UseSystemSettings(this);
 }
 }
@@ -218,7 +219,14 @@ void __fastcall TCopyDialog::AdjustControls()
   if (FLAGCLEAR(FOptions, coDoNotShowAgain))
   if (FLAGCLEAR(FOptions, coDoNotShowAgain))
   {
   {
     NeverShowAgainCheck->Visible = false;
     NeverShowAgainCheck->Visible = false;
-    ClientHeight = NeverShowAgainCheck->Top;
+    ClientHeight = ClientHeight -
+      (ShortCutHintPanel->Top - NeverShowAgainCheck->Top);
+  }
+
+  if (FLAGCLEAR(FOptions, coShortCutHint) || CustomWinConfiguration->CopyShortCutHintShown)
+  {
+    ShortCutHintPanel->Visible = false;
+    ClientHeight = ClientHeight - ShortCutHintPanel->Height;
   }
   }
 
 
   UpdateControls();
   UpdateControls();
@@ -359,6 +367,8 @@ void __fastcall TCopyDialog::FormShow(TObject * /*Sender*/)
 
 
   InstallPathWordBreakProc(RemoteDirectoryEdit);
   InstallPathWordBreakProc(RemoteDirectoryEdit);
   InstallPathWordBreakProc(LocalDirectoryEdit);
   InstallPathWordBreakProc(LocalDirectoryEdit);
+  // Does not work when set from a contructor
+  ShortCutHintPanel->Color = Application->HintColor;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 bool __fastcall TCopyDialog::Execute()
 bool __fastcall TCopyDialog::Execute()
@@ -381,6 +391,11 @@ bool __fastcall TCopyDialog::Execute()
       DirectoryEdit->SaveToHistory();
       DirectoryEdit->SaveToHistory();
       CustomWinConfiguration->History[FToRemote ?
       CustomWinConfiguration->History[FToRemote ?
         L"RemoteTarget" : L"LocalTarget"] = DirectoryEdit->Items;
         L"RemoteTarget" : L"LocalTarget"] = DirectoryEdit->Items;
+
+      if (FLAGSET(FOptions, coShortCutHint))
+      {
+        CustomWinConfiguration->CopyShortCutHintShown = true;
+      }
     }
     }
     __finally
     __finally
     {
     {
@@ -536,3 +551,8 @@ void __fastcall TCopyDialog::NeverShowAgainCheckClick(TObject * /*Sender*/)
   UpdateControls();
   UpdateControls();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TCopyDialog::ShortCutHintLabelClick(TObject * /*Sender*/)
+{
+  DoPreferencesDialog(pmCommander);
+}
+//---------------------------------------------------------------------------

+ 25 - 2
source/forms/Copy.dfm

@@ -6,7 +6,7 @@ object CopyDialog: TCopyDialog
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderStyle = bsDialog
   BorderStyle = bsDialog
   Caption = 'CopyDialog'
   Caption = 'CopyDialog'
-  ClientHeight = 189
+  ClientHeight = 225
   ClientWidth = 511
   ClientWidth = 511
   Color = clBtnFace
   Color = clBtnFace
   ParentFont = True
   ParentFont = True
@@ -16,7 +16,7 @@ object CopyDialog: TCopyDialog
   OnShow = FormShow
   OnShow = FormShow
   DesignSize = (
   DesignSize = (
     511
     511
-    189)
+    225)
   PixelsPerInch = 96
   PixelsPerInch = 96
   TextHeight = 13
   TextHeight = 13
   object MoveImage: TImage
   object MoveImage: TImage
@@ -945,4 +945,27 @@ object CopyDialog: TCopyDialog
       OnClick = CopyParamGroupClick
       OnClick = CopyParamGroupClick
     end
     end
   end
   end
+  object ShortCutHintPanel: TPanel
+    Left = 0
+    Top = 191
+    Width = 511
+    Height = 34
+    Align = alBottom
+    BevelOuter = bvNone
+    ParentBackground = False
+    TabOrder = 11
+    object ShortCutHintLabel: TLabel
+      Left = 12
+      Top = 3
+      Width = 490
+      Height = 28
+      AutoSize = False
+      Caption = 
+        'In Commander interface a keyboard shortcut F5 is used to transfe' +
+        'r files. Should you want to use it to refresh a file panel, clic' +
+        'k here to go to preferences.'
+      WordWrap = True
+      OnClick = ShortCutHintLabelClick
+    end
+  end
 end
 end

+ 3 - 0
source/forms/Copy.h

@@ -37,6 +37,8 @@ __published:
   TImage *CopyDownloadImage;
   TImage *CopyDownloadImage;
   TImage *MoveDownloadImage;
   TImage *MoveDownloadImage;
   TImage *MoveUploadImage;
   TImage *MoveUploadImage;
+  TPanel *ShortCutHintPanel;
+  TLabel *ShortCutHintLabel;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall FormCloseQuery(TObject *Sender, bool &CanClose);
   void __fastcall FormCloseQuery(TObject *Sender, bool &CanClose);
   void __fastcall LocalDirectoryBrowseButtonClick(TObject *Sender);
   void __fastcall LocalDirectoryBrowseButtonClick(TObject *Sender);
@@ -48,6 +50,7 @@ __published:
           TPoint &MousePos, bool &Handled);
           TPoint &MousePos, bool &Handled);
   void __fastcall TransferSettingsButtonDropDownClick(TObject *Sender);
   void __fastcall TransferSettingsButtonDropDownClick(TObject *Sender);
   void __fastcall NeverShowAgainCheckClick(TObject *Sender);
   void __fastcall NeverShowAgainCheckClick(TObject *Sender);
+  void __fastcall ShortCutHintLabelClick(TObject *Sender);
 private:
 private:
   bool FDefaultToRemote;
   bool FDefaultToRemote;
   bool FToRemote;
   bool FToRemote;

+ 1 - 1
source/forms/CustomCommand.cpp

@@ -47,7 +47,7 @@ __fastcall TCustomCommandDialog::TCustomCommandDialog(TComponent* Owner,
   FCustomCommandList = CustomCommandList;
   FCustomCommandList = CustomCommandList;
   FMode = Mode;
   FMode = Mode;
   FOnValidate = OnValidate;
   FOnValidate = OnValidate;
-  HintLabel(HintText, LoadStr(CUSTOM_COMMAND_PATTERNS_HINT3));
+  HintLabel(HintText, LoadStr(CUSTOM_COMMAND_PATTERNS_HINT4));
 
 
   int CaptionRes;
   int CaptionRes;
   switch (FMode)
   switch (FMode)

+ 64 - 55
source/forms/CustomScpExplorer.cpp

@@ -114,12 +114,14 @@ struct TTransferOperationParam
   UnicodeString TargetDirectory;
   UnicodeString TargetDirectory;
   bool Temp;
   bool Temp;
   bool DragDrop;
   bool DragDrop;
+  int Options;
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 TTransferOperationParam::TTransferOperationParam()
 TTransferOperationParam::TTransferOperationParam()
 {
 {
   Temp = false;
   Temp = false;
   DragDrop = false;
   DragDrop = false;
+  Options = 0;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TTransferPresetNoteData : public TObject
 class TTransferPresetNoteData : public TObject
@@ -534,7 +536,7 @@ void __fastcall TCustomScpExplorerForm::TerminalChanged()
   {
   {
     // this has to be set only after the tab is switched from TerminalListChanged,
     // this has to be set only after the tab is switched from TerminalListChanged,
     // otherwise we are changing color of wrong tab
     // otherwise we are changing color of wrong tab
-    SessionColor = ManagedTerminal->Color;
+    SessionColor = (TColor)ManagedTerminal->StateData->Color;
   }
   }
 
 
   UpdateTransferList();
   UpdateTransferList();
@@ -970,7 +972,7 @@ void __fastcall TCustomScpExplorerForm::EnableDDTransferConfirmation(TObject * /
 bool __fastcall TCustomScpExplorerForm::CopyParamDialog(
 bool __fastcall TCustomScpExplorerForm::CopyParamDialog(
   TTransferDirection Direction, TTransferType Type, bool Temp,
   TTransferDirection Direction, TTransferType Type, bool Temp,
   TStrings * FileList, UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam,
   TStrings * FileList, UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam,
-  bool Confirm, bool DragDrop)
+  bool Confirm, bool DragDrop, int Options)
 {
 {
   bool Result = true;
   bool Result = true;
   assert(Terminal && Terminal->Active);
   assert(Terminal && Terminal->Active);
@@ -1000,7 +1002,7 @@ bool __fastcall TCustomScpExplorerForm::CopyParamDialog(
     bool DisableNewerOnly =
     bool DisableNewerOnly =
       (!Terminal->IsCapable[fcNewerOnlyUpload] && (Direction == tdToRemote)) ||
       (!Terminal->IsCapable[fcNewerOnlyUpload] && (Direction == tdToRemote)) ||
       ToTemp;
       ToTemp;
-    int Options =
+    Options |=
       FLAGMASK(ToTemp, coTemp) |
       FLAGMASK(ToTemp, coTemp) |
       coDoNotShowAgain;
       coDoNotShowAgain;
     TUsableCopyParamAttrs UsableCopyParamAttrs = Terminal->UsableCopyParamAttrs(Params);
     TUsableCopyParamAttrs UsableCopyParamAttrs = Terminal->UsableCopyParamAttrs(Params);
@@ -2020,6 +2022,7 @@ bool __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Oper
       UnicodeString TargetDirectory;
       UnicodeString TargetDirectory;
       bool Temp = false;
       bool Temp = false;
       bool DragDrop = false;
       bool DragDrop = false;
+      int Options = 0;
       if (Param != NULL)
       if (Param != NULL)
       {
       {
         TTransferOperationParam& TParam =
         TTransferOperationParam& TParam =
@@ -2027,11 +2030,12 @@ bool __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Oper
         TargetDirectory = TParam.TargetDirectory;
         TargetDirectory = TParam.TargetDirectory;
         Temp = TParam.Temp;
         Temp = TParam.Temp;
         DragDrop = TParam.DragDrop;
         DragDrop = TParam.DragDrop;
+        Options = TParam.Options;
       }
       }
       TGUICopyParamType CopyParam = GUIConfiguration->CurrentCopyParam;
       TGUICopyParamType CopyParam = GUIConfiguration->CurrentCopyParam;
       Result =
       Result =
         CopyParamDialog(Direction, Type, Temp, FileList, TargetDirectory,
         CopyParamDialog(Direction, Type, Temp, FileList, TargetDirectory,
-          CopyParam, !NoConfirmation, DragDrop);
+          CopyParam, !NoConfirmation, DragDrop, Options);
       if (Result)
       if (Result)
       {
       {
         assert(Terminal);
         assert(Terminal);
@@ -2291,6 +2295,20 @@ void __fastcall TCustomScpExplorerForm::ExecuteFileOperationCommand(
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::ExecuteCopyOperationCommand(
+  TOperationSide Side, bool OnFocused, bool ShortCutHint)
+{
+  TTransferOperationParam Param;
+  if ((WinConfiguration->Interface != ifCommander) ||
+      WinConfiguration->ScpCommander.ExplorerKeyboardShortcuts)
+  {
+    ShortCutHint = false;
+  }
+  Param.Options =
+    FLAGMASK(ShortCutHint, coShortCutHint);
+  ExecuteFileOperationCommand(foCopy, Side, OnFocused, false, &Param);
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::HandleErrorList(TStringList *& ErrorList)
 void __fastcall TCustomScpExplorerForm::HandleErrorList(TStringList *& ErrorList)
 {
 {
   try
   try
@@ -3319,7 +3337,7 @@ bool __fastcall TCustomScpExplorerForm::RemoteTransferDialog(TTerminal *& Sessio
     else
     else
     {
     {
       assert(Terminal != NULL);
       assert(Terminal != NULL);
-      // update Terminal->RemoteDirectory
+      // update Terminal->StateData->RemoteDirectory
       UpdateTerminal(Terminal);
       UpdateTerminal(Terminal);
       TStrings * Sessions = TTerminalManager::Instance()->TerminalList;
       TStrings * Sessions = TTerminalManager::Instance()->TerminalList;
       TStrings * Directories = new TStringList;
       TStrings * Directories = new TStringList;
@@ -3329,7 +3347,7 @@ bool __fastcall TCustomScpExplorerForm::RemoteTransferDialog(TTerminal *& Sessio
         {
         {
           TManagedTerminal * Terminal =
           TManagedTerminal * Terminal =
             dynamic_cast<TManagedTerminal *>(Sessions->Objects[Index]);
             dynamic_cast<TManagedTerminal *>(Sessions->Objects[Index]);
-          Directories->Add(Terminal->RemoteDirectory);
+          Directories->Add(Terminal->StateData->RemoteDirectory);
         }
         }
 
 
         TDirectRemoteCopy AllowDirectCopy;
         TDirectRemoteCopy AllowDirectCopy;
@@ -3687,6 +3705,10 @@ void __fastcall TCustomScpExplorerForm::KeyDown(Word & Key, Classes::TShiftState
           AllowedAction(Action, aaShortCut))
           AllowedAction(Action, aaShortCut))
       {
       {
         Key = 0;
         Key = 0;
+        // Reset reference to previous component (when menu/toolbar was clicked).
+        // Needed to detect that action was invoked by keyboard shortcut
+        // in TNonVisualDataModule::ExplorerActionsExecute
+        Action->ActionComponent = NULL;
         Action->Execute();
         Action->Execute();
         return;
         return;
       }
       }
@@ -4788,14 +4810,12 @@ TObjectList * __fastcall TCustomScpExplorerForm::DoCollectWorkspace()
   TTerminalManager * Manager = TTerminalManager::Instance();
   TTerminalManager * Manager = TTerminalManager::Instance();
   std::unique_ptr<TObjectList> DataList(new TObjectList());
   std::unique_ptr<TObjectList> DataList(new TObjectList());
 
 
-  Manager->SaveWorkspace(DataList.get());
-
-  TSessionData * ActiveTerminalSessionData =
-    NOT_NULL(dynamic_cast<TSessionData *>(DataList->Items[Manager->ActiveTerminalIndex]));
-  if (ActiveTerminalSessionData->Link.IsEmpty())
+  if (ALWAYS_TRUE(Terminal != NULL))
   {
   {
-    UpdateSessionData(ActiveTerminalSessionData);
+    // Update (Managed)Terminal->StateData
+    UpdateTerminal(Terminal);
   }
   }
+  Manager->SaveWorkspace(DataList.get());
 
 
   return DataList.release();
   return DataList.release();
 }
 }
@@ -4917,7 +4937,7 @@ bool __fastcall TCustomScpExplorerForm::SaveWorkspace(bool EnableAutoSave)
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::UpdateTerminal(TTerminal * Terminal)
 void __fastcall TCustomScpExplorerForm::UpdateTerminal(TTerminal * Terminal)
 {
 {
-  TManagedTerminal * ManagedTerminal = dynamic_cast<TManagedTerminal *>(FTerminal);
+  TManagedTerminal * ManagedTerminal = dynamic_cast<TManagedTerminal *>(Terminal);
   assert(ManagedTerminal != NULL);
   assert(ManagedTerminal != NULL);
 
 
   SAFE_DESTROY(ManagedTerminal->RemoteExplorerState);
   SAFE_DESTROY(ManagedTerminal->RemoteExplorerState);
@@ -4927,27 +4947,18 @@ void __fastcall TCustomScpExplorerForm::UpdateTerminal(TTerminal * Terminal)
     ManagedTerminal->RemoteExplorerState = RemoteDirView->SaveState();
     ManagedTerminal->RemoteExplorerState = RemoteDirView->SaveState();
   }
   }
 
 
-  // cannot use RemoteDirView->Path, because it is empty if connection
-  // was already closed
-  // also 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 = Terminal->PeekCurrentDirectory();
-  if (!ACurrentDirectory.IsEmpty())
-  {
-    ManagedTerminal->RemoteDirectory = ACurrentDirectory;
-  }
-  ManagedTerminal->Color = SessionColor;
+  UpdateSessionData(ManagedTerminal->StateData);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::UpdateSessionData(TSessionData * Data)
 void __fastcall TCustomScpExplorerForm::UpdateSessionData(TSessionData * Data)
 {
 {
+  // Keep in sync with TSessionData::CopyStateData
+
   assert(Data != NULL);
   assert(Data != NULL);
 
 
   // cannot use RemoteDirView->Path, because it is empty if connection
   // cannot use RemoteDirView->Path, because it is empty if connection
   // was already closed
   // was already closed
   // also only peek, we may not be connected at all atm
   // also only peek, we may not be connected at all atm
-  // (well this can hardly be true here, as opposite to UpdateTerminal above),
   // so make sure we do not try retrieving current directory from the server
   // so make sure we do not try retrieving current directory from the server
   // (particularly with FTP)
   // (particularly with FTP)
   UnicodeString ACurrentDirectory = Terminal->PeekCurrentDirectory();
   UnicodeString ACurrentDirectory = Terminal->PeekCurrentDirectory();
@@ -5035,16 +5046,12 @@ void __fastcall TCustomScpExplorerForm::AddBookmark(TOperationSide Side)
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 TStrings * __fastcall TCustomScpExplorerForm::CreateVisitedDirectories(TOperationSide Side)
 TStrings * __fastcall TCustomScpExplorerForm::CreateVisitedDirectories(TOperationSide Side)
 {
 {
-  TStringList * VisitedDirectories = new TStringList();
+  // we should better use TCustomDirView::FCaseSensitive, but it is private
+  TStringList * VisitedDirectories = CreateSortedStringList((Side == osRemote));
   try
   try
   {
   {
     TCustomDirView * DView = DirView(Side);
     TCustomDirView * DView = DirView(Side);
 
 
-    VisitedDirectories->Duplicates = Types::dupIgnore;
-    // we should better use TCustomDirView::FCaseSensitive, but it is private
-    VisitedDirectories->CaseSensitive = (Side == osRemote);
-    VisitedDirectories->Sorted = true;
-
     for (int Index = -DView->BackCount; Index <= DView->ForwardCount; Index++)
     for (int Index = -DView->BackCount; Index <= DView->ForwardCount; Index++)
     {
     {
       VisitedDirectories->Add(DView->HistoryPath[Index]);
       VisitedDirectories->Add(DView->HistoryPath[Index]);
@@ -5451,7 +5458,7 @@ void __fastcall TCustomScpExplorerForm::UpdateSessionTab(TTabSheet * TabSheet)
     if (ALWAYS_TRUE(ManagedTerminal != NULL))
     if (ALWAYS_TRUE(ManagedTerminal != NULL))
     {
     {
       TColor Color =
       TColor Color =
-        (ManagedTerminal == FTerminal) ? FSessionColor : ManagedTerminal->Color;
+        (ManagedTerminal == FTerminal) ? FSessionColor : ManagedTerminal->StateData->Color;
 
 
       TabSheet->ImageIndex = AddSessionColor(Color);
       TabSheet->ImageIndex = AddSessionColor(Color);
 
 
@@ -6280,9 +6287,10 @@ void __fastcall TCustomScpExplorerForm::RemoteFileControlDDTargetDrop()
     TGUICopyParamType CopyParams = GUIConfiguration->CurrentCopyParam;
     TGUICopyParamType CopyParams = GUIConfiguration->CurrentCopyParam;
     // empty directory parameter means temp directory -> don't display it!
     // empty directory parameter means temp directory -> don't display it!
     UnicodeString TargetDir = L"";
     UnicodeString TargetDir = L"";
+    int Options = 0;
 
 
     if (!CopyParamDialog(tdToLocal, Type, true, FDDFileList,
     if (!CopyParamDialog(tdToLocal, Type, true, FDDFileList,
-          TargetDir, CopyParams, (WinConfiguration->DDTransferConfirmation != asOff), true))
+          TargetDir, CopyParams, (WinConfiguration->DDTransferConfirmation != asOff), true, Options))
     {
     {
       Abort();
       Abort();
     }
     }
@@ -6324,11 +6332,20 @@ void __fastcall TCustomScpExplorerForm::RemoteFileControlDDTargetDrop()
 void __fastcall TCustomScpExplorerForm::DDDownload(TStrings * FilesToCopy,
 void __fastcall TCustomScpExplorerForm::DDDownload(TStrings * FilesToCopy,
   const UnicodeString TargetDir, const TCopyParamType * CopyParam, int Params)
   const UnicodeString TargetDir, const TCopyParamType * CopyParam, int Params)
 {
 {
-  UpdateCopyParamCounters(*CopyParam);
-  Terminal->CopyToLocal(FilesToCopy, TargetDir, CopyParam, Params);
-  if (FLAGSET(Params, cpDelete) && (DropSourceControl == RemoteDriveView))
+  void * BatchStorage;
+  BatchStart(BatchStorage);
+  try
   {
   {
-    RemoteDriveView->UpdateDropSource();
+    UpdateCopyParamCounters(*CopyParam);
+    Terminal->CopyToLocal(FilesToCopy, TargetDir, CopyParam, Params);
+    if (FLAGSET(Params, cpDelete) && (DropSourceControl == RemoteDriveView))
+    {
+      RemoteDriveView->UpdateDropSource();
+    }
+  }
+  __finally
+  {
+    BatchEnd(BatchStorage);
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -7407,6 +7424,10 @@ void __fastcall TCustomScpExplorerForm::Dispatch(void * Message)
       M->Result = 1;
       M->Result = 1;
       break;
       break;
 
 
+    case WM_WANTS_MOUSEWHEEL:
+      M->Result = 1;
+      break;
+
     default:
     default:
       TForm::Dispatch(Message);
       TForm::Dispatch(Message);
       break;
       break;
@@ -8206,24 +8227,12 @@ void __fastcall TCustomScpExplorerForm::DisplaySystemContextMenu()
   FAIL;
   FAIL;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-bool __fastcall TCustomScpExplorerForm::HandleMouseWheel(WPARAM WParam, LPARAM LParam)
+Boolean __fastcall TCustomScpExplorerForm::AllowedAction(TAction * /*Action*/, TActionAllowed Allowed)
 {
 {
-  bool Result = Active;
-  if (Result)
-  {
-    TPoint Point(LOWORD(LParam), HIWORD(LParam));
-    TWinControl * Control = FindVCLWindow(Point);
-    Result = (Control != NULL);
-    if (Result)
-    {
-      TCustomForm * Form = ValidParentForm(Control);
-      Result = (Form == this);
-      if (Result)
-      {
-        SendMessage(Control->Handle, WM_MOUSEWHEEL, WParam, LParam);
-      }
-    }
-  }
-  return Result;
+  // While the window is disabled, we still seem to process menu shortcuts at least,
+  // so stop it at least here
+  return
+    (Allowed == aaUpdate) ||
+    (FLockLevel == 0);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 4 - 3
source/forms/CustomScpExplorer.h

@@ -313,7 +313,7 @@ protected:
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool Temp, TStrings * FileList,
     TTransferType Type, bool Temp, TStrings * FileList,
     UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
     UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
-    bool DragDrop);
+    bool DragDrop, int Options);
   virtual bool __fastcall RemoteTransferDialog(TTerminal *& Session,
   virtual bool __fastcall RemoteTransferDialog(TTerminal *& Session,
     TStrings * FileList, UnicodeString & Target, UnicodeString & FileMask, bool & DirectCopy,
     TStrings * FileList, UnicodeString & Target, UnicodeString & FileMask, bool & DirectCopy,
     bool NoConfirmation, bool Move);
     bool NoConfirmation, bool Move);
@@ -538,11 +538,13 @@ public:
   virtual void __fastcall AddEditLink(TOperationSide Side, bool Add);
   virtual void __fastcall AddEditLink(TOperationSide Side, bool Add);
   bool __fastcall CanAddEditLink(TOperationSide Side);
   bool __fastcall CanAddEditLink(TOperationSide Side);
   bool __fastcall LinkFocused();
   bool __fastcall LinkFocused();
-  virtual Boolean __fastcall AllowedAction(TAction * Action, TActionAllowed Allowed) = 0;
+  virtual Boolean __fastcall AllowedAction(TAction * Action, TActionAllowed Allowed);
   virtual void __fastcall ConfigurationChanged();
   virtual void __fastcall ConfigurationChanged();
   void __fastcall CreateDirectory(TOperationSide Side);
   void __fastcall CreateDirectory(TOperationSide Side);
   void __fastcall ExecuteFileOperationCommand(TFileOperation Operation, TOperationSide Side,
   void __fastcall ExecuteFileOperationCommand(TFileOperation Operation, TOperationSide Side,
     bool OnFocused, bool NoConfirmation = false, void * Param = NULL);
     bool OnFocused, bool NoConfirmation = false, void * Param = NULL);
+  void __fastcall ExecuteCopyOperationCommand(
+    TOperationSide Side, bool OnFocused, bool ShortCutHint);
   void __fastcall AdHocCustomCommand(bool OnFocused);
   void __fastcall AdHocCustomCommand(bool OnFocused);
   virtual TCustomDirView * __fastcall DirView(TOperationSide Side);
   virtual TCustomDirView * __fastcall DirView(TOperationSide Side);
   virtual void __fastcall ChangePath(TOperationSide Side) = 0;
   virtual void __fastcall ChangePath(TOperationSide Side) = 0;
@@ -638,7 +640,6 @@ public:
   virtual void __fastcall HistoryGo(TOperationSide Side, int Index);
   virtual void __fastcall HistoryGo(TOperationSide Side, int Index);
   void __fastcall UpdateTaskbarList(ITaskbarList3 * TaskbarList);
   void __fastcall UpdateTaskbarList(ITaskbarList3 * TaskbarList);
   virtual void __fastcall DisplaySystemContextMenu();
   virtual void __fastcall DisplaySystemContextMenu();
-  bool __fastcall HandleMouseWheel(WPARAM WParam, LPARAM LParam);
 
 
   __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
   __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
   __property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };
   __property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };

+ 12 - 3
source/forms/FileSystemInfo.cpp

@@ -206,9 +206,9 @@ void __fastcall TFileSystemInfoDialog::FeedControls()
 {
 {
   FLastFeededControl = NULL;
   FLastFeededControl = NULL;
   Feed(ControlsAddItem);
   Feed(ControlsAddItem);
-  AdjustListColumnsWidth(ServerView);
-  AdjustListColumnsWidth(ProtocolView);
-  AdjustListColumnsWidth(SpaceAvailableView);
+  AutoSizeListColumnsWidth(ServerView, 1);
+  AutoSizeListColumnsWidth(ProtocolView, 1);
+  AutoSizeListColumnsWidth(SpaceAvailableView, 1);
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
 void __fastcall TFileSystemInfoDialog::UpdateControls()
 void __fastcall TFileSystemInfoDialog::UpdateControls()
@@ -374,3 +374,12 @@ void __fastcall TFileSystemInfoDialog::CertificateViewButtonClick(
   MessageDialog(FSessionInfo.Certificate, qtInformation, qaOK);
   MessageDialog(FSessionInfo.Certificate, qtInformation, qaOK);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TFileSystemInfoDialog::SpaceAvailableViewCustomDrawItem(
+  TCustomListView * Sender, TListItem * Item, TCustomDrawState /*State*/, bool & /*DefaultDraw*/)
+{
+  if ((Item->SubItems->Count >= 1) && (Item->SubItems->Strings[0] == LoadStr(FSINFO_BYTES_UNKNOWN)))
+  {
+    Sender->Canvas->Font->Color = clGrayText;
+  }
+}
+//---------------------------------------------------------------------------

+ 7 - 6
source/forms/FileSystemInfo.dfm

@@ -88,12 +88,12 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
         Anchors = [akLeft, akTop, akRight, akBottom]
         Anchors = [akLeft, akTop, akRight, akBottom]
         Columns = <
         Columns = <
           item
           item
-            AutoSize = True
             Caption = 'Item'
             Caption = 'Item'
+            Width = 150
           end
           end
           item
           item
-            AutoSize = True
             Caption = 'Value'
             Caption = 'Value'
+            Width = 150
           end>
           end>
         ColumnClick = False
         ColumnClick = False
         DoubleBuffered = True
         DoubleBuffered = True
@@ -184,12 +184,12 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
         Anchors = [akLeft, akTop, akRight, akBottom]
         Anchors = [akLeft, akTop, akRight, akBottom]
         Columns = <
         Columns = <
           item
           item
-            AutoSize = True
             Caption = 'Item'
             Caption = 'Item'
+            Width = 150
           end
           end
           item
           item
-            AutoSize = True
             Caption = 'Value'
             Caption = 'Value'
+            Width = 150
           end>
           end>
         ColumnClick = False
         ColumnClick = False
         DoubleBuffered = True
         DoubleBuffered = True
@@ -225,12 +225,12 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
         Anchors = [akLeft, akTop, akRight, akBottom]
         Anchors = [akLeft, akTop, akRight, akBottom]
         Columns = <
         Columns = <
           item
           item
-            AutoSize = True
             Caption = 'Item'
             Caption = 'Item'
+            Width = 150
           end
           end
           item
           item
-            AutoSize = True
             Caption = 'Value'
             Caption = 'Value'
+            Width = 150
           end>
           end>
         ColumnClick = False
         ColumnClick = False
         DoubleBuffered = True
         DoubleBuffered = True
@@ -242,6 +242,7 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
         TabOrder = 2
         TabOrder = 2
         ViewStyle = vsReport
         ViewStyle = vsReport
         OnContextPopup = ControlContextPopup
         OnContextPopup = ControlContextPopup
+        OnCustomDrawItem = SpaceAvailableViewCustomDrawItem
       end
       end
       object SpaceAvailablePathEdit: TEdit
       object SpaceAvailablePathEdit: TEdit
         Left = 56
         Left = 56

+ 2 - 0
source/forms/FileSystemInfo.h

@@ -48,6 +48,8 @@ __published:
   void __fastcall ControlContextPopup(TObject *Sender, TPoint &MousePos,
   void __fastcall ControlContextPopup(TObject *Sender, TPoint &MousePos,
           bool &Handled);
           bool &Handled);
   void __fastcall CertificateViewButtonClick(TObject *Sender);
   void __fastcall CertificateViewButtonClick(TObject *Sender);
+  void __fastcall SpaceAvailableViewCustomDrawItem(TCustomListView *Sender, TListItem *Item,
+          TCustomDrawState State, bool &DefaultDraw);
 public:
 public:
   virtual __fastcall TFileSystemInfoDialog(TComponent * AOwner,
   virtual __fastcall TFileSystemInfoDialog(TComponent * AOwner,
     TGetSpaceAvailable OnGetSpaceAvailable);
     TGetSpaceAvailable OnGetSpaceAvailable);

+ 6 - 34
source/forms/GenerateUrl.cpp

@@ -33,40 +33,12 @@ __fastcall TGenerateUrlDialog::TGenerateUrlDialog(
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TGenerateUrlDialog::GenerateUrl(UnicodeString Path)
 UnicodeString __fastcall TGenerateUrlDialog::GenerateUrl(UnicodeString Path)
 {
 {
-  UnicodeString Url;
-
-  if (WinSCPSpecificCheck->Checked)
-  {
-    Url += WinSCPProtocolPrefix;
-  }
-
-  Url += FData->ProtocolUrl;
-
-  if (UserNameCheck->Enabled && UserNameCheck->Checked)
-  {
-    Url += EncodeUrlString(FData->UserNameExpanded);
-
-    if (PasswordCheck->Enabled && PasswordCheck->Checked)
-    {
-      Url += L":" + EncodeUrlString(FData->Password);
-    }
-
-    if (HostKeyCheck->Enabled && HostKeyCheck->Checked)
-    {
-      Url +=
-        UnicodeString(UrlParamSeparator) + UrlHostKeyParamName +
-        UnicodeString(UrlParamValueSeparator) + NormalizeFingerprint(FData->HostKey);
-    }
-
-    Url += L"@";
-  }
-
-  Url += EncodeUrlString(FData->HostNameExpanded);
-  if (FData->PortNumber != DefaultPort(FData->FSProtocol, FData->Ftps))
-  {
-    Url += L":" + IntToStr(FData->PortNumber);
-  }
-  Url += L"/";
+  UnicodeString Url =
+    FData->GenerateSessionUrl(
+      FLAGMASK(WinSCPSpecificCheck->Checked, sufSpecific) |
+      FLAGMASK(UserNameCheck->Enabled && UserNameCheck->Checked, sufUserName) |
+      FLAGMASK(PasswordCheck->Enabled && PasswordCheck->Checked, sufPassword) |
+      FLAGMASK(HostKeyCheck->Enabled && HostKeyCheck->Checked, sufHostKey));
 
 
   if ((RemoteDirectoryCheck->Enabled && RemoteDirectoryCheck->Checked) ||
   if ((RemoteDirectoryCheck->Enabled && RemoteDirectoryCheck->Checked) ||
       (FPaths != NULL))
       (FPaths != NULL))

+ 1 - 1
source/forms/ImportSessions.cpp

@@ -122,7 +122,7 @@ void __fastcall TImportSessionsDialog::UpdateControls()
   EnableControl(ImportKeysCheck, AnySshChecked);
   EnableControl(ImportKeysCheck, AnySshChecked);
 
 
   EnableControl(CheckAllButton, SessionListView2->Items->Count > 0);
   EnableControl(CheckAllButton, SessionListView2->Items->Count > 0);
-  AdjustListColumnsWidth(SessionListView2);
+  AutoSizeListColumnsWidth(SessionListView2);
 }
 }
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------
 void __fastcall TImportSessionsDialog::ClearSelections()
 void __fastcall TImportSessionsDialog::ClearSelections()

+ 2 - 8
source/forms/LocationProfiles.cpp

@@ -191,14 +191,8 @@ __fastcall TLocationProfilesDialog::TLocationProfilesDialog(TComponent * AOwner)
   FSharedBookmarkList = new TBookmarkList();
   FSharedBookmarkList = new TBookmarkList();
   FChanging = false;
   FChanging = false;
 
 
-  FSessionFolders = new TStringList;
-  FSessionFolders->CaseSensitive = false;
-  FSessionFolders->Sorted = true;
-  FSessionFolders->Duplicates = Types::dupIgnore;
-  FSharedFolders = new TStringList;
-  FSharedFolders->CaseSensitive = false;
-  FSharedFolders->Sorted = true;
-  FSharedFolders->Duplicates = Types::dupIgnore;
+  FSessionFolders = CreateSortedStringList();
+  FSharedFolders = CreateSortedStringList();
 
 
   FSessionScrollOnDragOver = new TTreeViewScrollOnDragOver(SessionProfilesView, true);
   FSessionScrollOnDragOver = new TTreeViewScrollOnDragOver(SessionProfilesView, true);
   FSharedScrollOnDragOver = new TTreeViewScrollOnDragOver(SharedProfilesView, true);
   FSharedScrollOnDragOver = new TTreeViewScrollOnDragOver(SharedProfilesView, true);

+ 12 - 25
source/forms/Login.cpp

@@ -624,15 +624,6 @@ void __fastcall TLoginDialog::UpdateControls()
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-void __fastcall TLoginDialog::DefaultButton(TButton * Button, bool Default)
-{
-  // default property setter does not have guard for "the same value"
-  if (Button->Default != Default)
-  {
-    Button->Default = Default;
-  }
-}
-//---------------------------------------------------------------------------
 void __fastcall TLoginDialog::UpdateButtonVisibility(TButton * Button)
 void __fastcall TLoginDialog::UpdateButtonVisibility(TButton * Button)
 {
 {
   TAction * Action = NOT_NULL(dynamic_cast<TAction *>(Button->Action));
   TAction * Action = NOT_NULL(dynamic_cast<TAction *>(Button->Action));
@@ -1251,7 +1242,7 @@ bool __fastcall TLoginDialog::Execute(TList * DataList)
   FSortEnablePending = true;
   FSortEnablePending = true;
   // Not calling LoadState here.
   // Not calling LoadState here.
   // It's redundant and does not work anyway, see comment in the method.
   // It's redundant and does not work anyway, see comment in the method.
-  bool Result = IsDefaultResult(ShowModal());
+  bool Result = (ShowModal() == DefaultResult());
   SaveState();
   SaveState();
   if (Result)
   if (Result)
   {
   {
@@ -1332,13 +1323,10 @@ void __fastcall TLoginDialog::SaveState()
 {
 {
   assert(WinConfiguration != NULL);
   assert(WinConfiguration != NULL);
 
 
-  TStringList * OpenedStoredSessionFolders = new TStringList();
+  TStringList * OpenedStoredSessionFolders = CreateSortedStringList();
   WinConfiguration->BeginUpdate();
   WinConfiguration->BeginUpdate();
   try
   try
   {
   {
-    OpenedStoredSessionFolders->CaseSensitive = false;
-    OpenedStoredSessionFolders->Sorted = true;
-
     for (int Index = 0; Index < SessionTree->Items->Count; Index++)
     for (int Index = 0; Index < SessionTree->Items->Count; Index++)
     {
     {
       SaveOpenedStoredSessionFolders(
       SaveOpenedStoredSessionFolders(
@@ -1395,11 +1383,9 @@ void __fastcall TLoginDialog::LoadState()
     }
     }
   }
   }
 
 
-  TStringList * OpenedStoredSessionFolders = new TStringList();
+  TStringList * OpenedStoredSessionFolders = CreateSortedStringList();
   try
   try
   {
   {
-    OpenedStoredSessionFolders->CaseSensitive = false;
-    OpenedStoredSessionFolders->Sorted = true;
     OpenedStoredSessionFolders->CommaText = WinConfiguration->OpenedStoredSessionFolders;
     OpenedStoredSessionFolders->CommaText = WinConfiguration->OpenedStoredSessionFolders;
 
 
     for (int Index = 0; Index < SessionTree->Items->Count; Index++)
     for (int Index = 0; Index < SessionTree->Items->Count; Index++)
@@ -1589,6 +1575,10 @@ void __fastcall TLoginDialog::Dispatch(void * Message)
     // caption managed in TLoginDialog::Init()
     // caption managed in TLoginDialog::Init()
     M->Result = 1;
     M->Result = 1;
   }
   }
+  else if (M->Msg == WM_WANTS_MOUSEWHEEL)
+  {
+    M->Result = 1;
+  }
   else if (M->Msg == CM_ACTIVATE)
   else if (M->Msg == CM_ACTIVATE)
   {
   {
     // Called from TCustomForm.ShowModal
     // Called from TCustomForm.ShowModal
@@ -1738,21 +1728,18 @@ void __fastcall TLoginDialog::HelpButtonClick(TObject * /*Sender*/)
   FormHelp(this);
   FormHelp(this);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-bool __fastcall TLoginDialog::IsDefaultResult(TModalResult Result)
+TModalResult __fastcall TLoginDialog::DefaultResult()
 {
 {
-  return
-    // When editing or when hostname-less site is selected,
-    // there's no default button, so make sure we do not call DefaultResult
-    (FindDefaultButton(this) != NULL) &&
-    (FindDefaultButton(this)->ModalResult != mrNone) &&
-    (Result == DefaultResult(this));
+  return ::DefaultResult(this, LoginButton);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TLoginDialog::FormCloseQuery(TObject * /*Sender*/,
 void __fastcall TLoginDialog::FormCloseQuery(TObject * /*Sender*/,
   bool & CanClose)
   bool & CanClose)
 {
 {
+  // CanClose test is now probably redundant,
+  // once we have a fallback to LoginButton in DefaultResult
   CanClose = EnsureNotEditing();
   CanClose = EnsureNotEditing();
-  if (CanClose && IsDefaultResult(ModalResult))
+  if (CanClose && (ModalResult == DefaultResult()))
   {
   {
     SaveDataList(FDataList);
     SaveDataList(FDataList);
   }
   }

+ 1 - 1
source/forms/Login.h

@@ -340,7 +340,6 @@ private:
   TTreeNode * __fastcall FindSessionNode(TSessionData * SessionData, bool ByName);
   TTreeNode * __fastcall FindSessionNode(TSessionData * SessionData, bool ByName);
   void __fastcall UpdateButtonVisibility(TButton * Button);
   void __fastcall UpdateButtonVisibility(TButton * Button);
   void __fastcall Idle();
   void __fastcall Idle();
-  void __fastcall DefaultButton(TButton * Button, bool Default);
   TSessionData * __fastcall GetEditingSessionData();
   TSessionData * __fastcall GetEditingSessionData();
   void __fastcall SaveAsSession(bool ForceDialog);
   void __fastcall SaveAsSession(bool ForceDialog);
   void __fastcall InvalidateSessionData();
   void __fastcall InvalidateSessionData();
@@ -358,6 +357,7 @@ private:
   void __fastcall ParseUrl(const UnicodeString & Url);
   void __fastcall ParseUrl(const UnicodeString & Url);
   void __fastcall ParseHostName();
   void __fastcall ParseHostName();
   void __fastcall ResetNewSiteData();
   void __fastcall ResetNewSiteData();
+  TModalResult __fastcall DefaultResult();
 
 
 protected:
 protected:
   void __fastcall Default();
   void __fastcall Default();

+ 1 - 1
source/forms/MessageDlg.cpp

@@ -531,7 +531,7 @@ void __fastcall TMessageForm::ButtonDropDownClick(TObject * /*Sender*/)
 {
 {
   // as optimization, do not waste time running timer, unless
   // as optimization, do not waste time running timer, unless
   // user pops up drop down menu. we do not have a way to stop timer, once
   // user pops up drop down menu. we do not have a way to stop timer, once
-  // it closes, but functionaly is does not matter
+  // it closes, but functionaly it does not matter
   if (FUpdateForShiftStateTimer == NULL)
   if (FUpdateForShiftStateTimer == NULL)
   {
   {
     FUpdateForShiftStateTimer = new TTimer(this);
     FUpdateForShiftStateTimer = new TTimer(this);

+ 8 - 8
source/forms/NonVisual.cpp

@@ -463,6 +463,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPDQUEUE(ItemDown)
   UPDQUEUE(ItemDown)
   UPDQUEUE(PauseAll)
   UPDQUEUE(PauseAll)
   UPDQUEUE(ResumeAll)
   UPDQUEUE(ResumeAll)
+  UPDQUEUE(DeleteAll)
   UPDQUEUE(DeleteAllDone)
   UPDQUEUE(DeleteAllDone)
   #undef UPDQUEUE
   #undef UPDQUEUE
   UPDEX(QueueItemSpeedAction, ScpExplorer->AllowQueueOperation(qoItemSpeed, &Param),
   UPDEX(QueueItemSpeedAction, ScpExplorer->AllowQueueOperation(qoItemSpeed, &Param),
@@ -501,6 +502,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
   }
   }
   ScpExplorer->BeforeAction();
   ScpExplorer->BeforeAction();
 
 
+  bool ShortCutAction = (Action->ActionComponent == NULL);
 
 
   {
   {
     TAutoNestingCounter Counter(FBusy);
     TAutoNestingCounter Counter(FBusy);
@@ -531,7 +533,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXE(FileGenerateUrlAction, ScpExplorer->FileGenerateUrl())
     EXE(FileGenerateUrlAction, ScpExplorer->FileGenerateUrl())
     EXE(FileListFromClipboardAction, ScpExplorer->FileListFromClipboard())
     EXE(FileListFromClipboardAction, ScpExplorer->FileListFromClipboard())
     // local selected operation
     // local selected operation
-    EXE(LocalCopyAction, ScpExplorer->ExecuteFileOperationCommand(foCopy, osLocal, false))
+    EXE(LocalCopyAction, ScpExplorer->ExecuteCopyOperationCommand(osLocal, false, ShortCutAction))
     EXE(LocalRenameAction, ScpExplorer->ExecuteFileOperationCommand(foRename, osLocal, false))
     EXE(LocalRenameAction, ScpExplorer->ExecuteFileOperationCommand(foRename, osLocal, false))
     EXE(LocalEditAction, ScpExplorer->ExecuteFile(osLocal, efDefaultEditor, NULL, true, false))
     EXE(LocalEditAction, ScpExplorer->ExecuteFile(osLocal, efDefaultEditor, NULL, true, false))
     EXE(LocalMoveAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osLocal, false))
     EXE(LocalMoveAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osLocal, false))
@@ -540,10 +542,10 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXE(LocalPropertiesAction, ScpExplorer->ExecuteFileOperationCommand(foSetProperties, osLocal, false))
     EXE(LocalPropertiesAction, ScpExplorer->ExecuteFileOperationCommand(foSetProperties, osLocal, false))
     EXE(LocalAddEditLinkAction, ScpExplorer->AddEditLink(osLocal, false))
     EXE(LocalAddEditLinkAction, ScpExplorer->AddEditLink(osLocal, false))
     // local focused operation
     // local focused operation
-    EXE(LocalCopyFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foCopy, osLocal, true))
+    EXE(LocalCopyFocusedAction, ScpExplorer->ExecuteCopyOperationCommand(osLocal, true, ShortCutAction))
     EXE(LocalMoveFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osLocal, true))
     EXE(LocalMoveFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osLocal, true))
     // remote selected operation
     // remote selected operation
-    EXE(RemoteCopyAction, ScpExplorer->ExecuteFileOperationCommand(foCopy, osRemote, false))
+    EXE(RemoteCopyAction, ScpExplorer->ExecuteCopyOperationCommand(osRemote, false, ShortCutAction))
     EXE(RemoteRenameAction, ScpExplorer->ExecuteFileOperationCommand(foRename, osRemote, false))
     EXE(RemoteRenameAction, ScpExplorer->ExecuteFileOperationCommand(foRename, osRemote, false))
     EXE(RemoteEditAction, ScpExplorer->ExecuteFile(osRemote, efDefaultEditor, NULL, true, false))
     EXE(RemoteEditAction, ScpExplorer->ExecuteFile(osRemote, efDefaultEditor, NULL, true, false))
     EXE(RemoteMoveAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osRemote, false))
     EXE(RemoteMoveAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osRemote, false))
@@ -554,7 +556,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXE(RemoteCopyToAction, ScpExplorer->ExecuteFileOperationCommand(foRemoteCopy, osCurrent, false))
     EXE(RemoteCopyToAction, ScpExplorer->ExecuteFileOperationCommand(foRemoteCopy, osCurrent, false))
     EXE(RemoteAddEditLinkAction, ScpExplorer->AddEditLink(osRemote, false))
     EXE(RemoteAddEditLinkAction, ScpExplorer->AddEditLink(osRemote, false))
     // remote focused operation
     // remote focused operation
-    EXE(RemoteCopyFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foCopy, osRemote, true))
+    EXE(RemoteCopyFocusedAction, ScpExplorer->ExecuteCopyOperationCommand(osRemote, true, ShortCutAction))
     EXE(RemoteMoveFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osRemote, true))
     EXE(RemoteMoveFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foMove, osRemote, true))
     EXE(RemoteMoveToFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foRemoteMove, osCurrent, true))
     EXE(RemoteMoveToFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foRemoteMove, osCurrent, true))
     EXE(RemoteCopyToFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foRemoteCopy, osCurrent, true))
     EXE(RemoteCopyToFocusedAction, ScpExplorer->ExecuteFileOperationCommand(foRemoteCopy, osCurrent, true))
@@ -774,6 +776,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXEQUEUE(ItemDown)
     EXEQUEUE(ItemDown)
     EXEQUEUE(PauseAll)
     EXEQUEUE(PauseAll)
     EXEQUEUE(ResumeAll)
     EXEQUEUE(ResumeAll)
+    EXEQUEUE(DeleteAll)
     EXEQUEUE(DeleteAllDone)
     EXEQUEUE(DeleteAllDone)
     #undef EXEQUEUE
     #undef EXEQUEUE
     EXE(QueueToggleShowAction, ScpExplorer->ToggleQueueVisibility())
     EXE(QueueToggleShowAction, ScpExplorer->ToggleQueueVisibility())
@@ -1391,12 +1394,9 @@ void __fastcall TNonVisualDataModule::CreateEditorListMenu(TAction * Action, boo
 
 
     AddMenuSeparator(Menu);
     AddMenuSeparator(Menu);
 
 
-    TStringList * UsedEditors = new TStringList();
+    TStringList * UsedEditors = CreateSortedStringList();
     try
     try
     {
     {
-      UsedEditors->CaseSensitive = false;
-      UsedEditors->Sorted = true;
-
       const TEditorList * EditorList = WinConfiguration->EditorList;
       const TEditorList * EditorList = WinConfiguration->EditorList;
       for (int Index = 0; Index < EditorList->Count; Index++)
       for (int Index = 0; Index < EditorList->Count; Index++)
       {
       {

+ 10 - 0
source/forms/NonVisual.dfm

@@ -1915,6 +1915,13 @@ object NonVisualDataModule: TNonVisualDataModule
       Hint = 'Change speed limit of selected queue item'
       Hint = 'Change speed limit of selected queue item'
       EditCaption = '&Speed'
       EditCaption = '&Speed'
     end
     end
+    object QueueDeleteAllAction: TAction
+      Tag = 12
+      Category = 'Queue'
+      Caption = '&Cancel All'
+      HelpKeyword = 'ui_queue#managing_the_queue'
+      Hint = 'Remove all queue items'
+    end
     object LocalFilterAction: TAction
     object LocalFilterAction: TAction
       Tag = 9
       Tag = 9
       Category = 'Local Directory'
       Category = 'Local Directory'
@@ -2621,6 +2628,9 @@ object NonVisualDataModule: TNonVisualDataModule
       object TBXItem12: TTBXItem
       object TBXItem12: TTBXItem
         Action = QueueResumeAllAction
         Action = QueueResumeAllAction
       end
       end
+      object TBXItem142: TTBXItem
+        Action = QueueDeleteAllAction
+      end
       object TBXSeparatorItem5: TTBXSeparatorItem
       object TBXSeparatorItem5: TTBXSeparatorItem
       end
       end
       object TBXItem51: TTBXItem
       object TBXItem51: TTBXItem

+ 2 - 0
source/forms/NonVisual.h

@@ -594,6 +594,8 @@ __published:    // IDE-managed Components
   TTBXSubmenuItem *TBXSubmenuItem7;
   TTBXSubmenuItem *TBXSubmenuItem7;
   TTBXItem *TBXItem70;
   TTBXItem *TBXItem70;
   TTBXItem *TBXItem71;
   TTBXItem *TBXItem71;
+  TAction *QueueDeleteAllAction;
+  TTBXItem *TBXItem142;
   void __fastcall LogActionsUpdate(TBasicAction *Action, bool &Handled);
   void __fastcall LogActionsUpdate(TBasicAction *Action, bool &Handled);
   void __fastcall LogActionsExecute(TBasicAction *Action, bool &Handled);
   void __fastcall LogActionsExecute(TBasicAction *Action, bool &Handled);
   void __fastcall ExplorerActionsUpdate(TBasicAction *Action, bool &Handled);
   void __fastcall ExplorerActionsUpdate(TBasicAction *Action, bool &Handled);

+ 8 - 3
source/forms/Preferences.cpp

@@ -73,8 +73,12 @@ __fastcall TPreferencesDialog::TPreferencesDialog(
   UseSystemSettings(this);
   UseSystemSettings(this);
 
 
   FCustomCommandsScrollOnDragOver = new TListViewScrollOnDragOver(CustomCommandsView, true);
   FCustomCommandsScrollOnDragOver = new TListViewScrollOnDragOver(CustomCommandsView, true);
+  FixListColumnWidth(CustomCommandsView, -2);
+  FixListColumnWidth(CustomCommandsView, -1);
   FCopyParamScrollOnDragOver = new TListViewScrollOnDragOver(CopyParamListView, true);
   FCopyParamScrollOnDragOver = new TListViewScrollOnDragOver(CopyParamListView, true);
+  FixListColumnWidth(CopyParamListView, -1);
   FEditorScrollOnDragOver = new TListViewScrollOnDragOver(EditorListView3, true);
   FEditorScrollOnDragOver = new TListViewScrollOnDragOver(EditorListView3, true);
+  FixListColumnWidth(EditorListView3, -1);
 
 
   ComboAutoSwitchInitialize(UpdatesBetaVersionsCombo);
   ComboAutoSwitchInitialize(UpdatesBetaVersionsCombo);
   EnableControl(UpdatesBetaVersionsCombo, !WinConfiguration->IsBeta);
   EnableControl(UpdatesBetaVersionsCombo, !WinConfiguration->IsBeta);
@@ -959,6 +963,7 @@ void __fastcall TPreferencesDialog::FormShow(TObject * /*Sender*/)
     case pmUpdates: PageControl->ActivePage = UpdatesSheet; break;
     case pmUpdates: PageControl->ActivePage = UpdatesSheet; break;
     case pmPresets: PageControl->ActivePage = CopyParamListSheet; break;
     case pmPresets: PageControl->ActivePage = CopyParamListSheet; break;
     case pmEditors: PageControl->ActivePage = EditorSheet; break;
     case pmEditors: PageControl->ActivePage = EditorSheet; break;
+    case pmCommander: PageControl->ActivePage = CommanderSheet; break;
     default: PageControl->ActivePage = PreferencesSheet; break;
     default: PageControl->ActivePage = PreferencesSheet; break;
   }
   }
   PageControlChange(NULL);
   PageControlChange(NULL);
@@ -1302,7 +1307,7 @@ void __fastcall TPreferencesDialog::ListViewSelectItem(
 void __fastcall TPreferencesDialog::UpdateCustomCommandsView()
 void __fastcall TPreferencesDialog::UpdateCustomCommandsView()
 {
 {
   CustomCommandsView->Items->Count = FCustomCommandList->Count;
   CustomCommandsView->Items->Count = FCustomCommandList->Count;
-  AdjustListColumnsWidth(CustomCommandsView);
+  AutoSizeListColumnsWidth(CustomCommandsView);
   CustomCommandsView->Invalidate();
   CustomCommandsView->Invalidate();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -1715,7 +1720,7 @@ void __fastcall TPreferencesDialog::EditorListView3KeyDown(TObject * /*Sender*/,
 void __fastcall TPreferencesDialog::UpdateEditorListView()
 void __fastcall TPreferencesDialog::UpdateEditorListView()
 {
 {
   EditorListView3->Items->Count = FEditorList->Count;
   EditorListView3->Items->Count = FEditorList->Count;
-  AdjustListColumnsWidth(EditorListView3);
+  AutoSizeListColumnsWidth(EditorListView3);
   EditorListView3->Invalidate();
   EditorListView3->Invalidate();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -1899,7 +1904,7 @@ void __fastcall TPreferencesDialog::EditorFontLabelDblClick(
 void __fastcall TPreferencesDialog::UpdateCopyParamListView()
 void __fastcall TPreferencesDialog::UpdateCopyParamListView()
 {
 {
   CopyParamListView->Items->Count = 1 + FCopyParamList->Count;
   CopyParamListView->Items->Count = 1 + FCopyParamList->Count;
-  AdjustListColumnsWidth(CopyParamListView);
+  AutoSizeListColumnsWidth(CopyParamListView);
   CopyParamListView->Invalidate();
   CopyParamListView->Invalidate();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 1 - 5
source/forms/Preferences.dfm

@@ -1235,7 +1235,7 @@ object PreferencesDialog: TPreferencesDialog
             Top = 95
             Top = 95
             Width = 85
             Width = 85
             Height = 13
             Height = 13
-            Caption = 'Default &encoding:'
+            Caption = 'Default en&coding:'
             FocusControl = EditorEncodingCombo
             FocusControl = EditorEncodingCombo
           end
           end
           object EditorFontButton: TButton
           object EditorFontButton: TButton
@@ -1312,7 +1312,6 @@ object PreferencesDialog: TPreferencesDialog
               end
               end
               item
               item
                 Caption = 'Text'
                 Caption = 'Text'
-                Tag = 1
                 Width = 45
                 Width = 45
               end>
               end>
             ColumnClick = False
             ColumnClick = False
@@ -1512,12 +1511,10 @@ object PreferencesDialog: TPreferencesDialog
               end
               end
               item
               item
                 Caption = 'L/R'
                 Caption = 'L/R'
-                Tag = 1
                 Width = 35
                 Width = 35
               end
               end
               item
               item
                 Caption = 'D/R'
                 Caption = 'D/R'
-                Tag = 1
                 Width = 40
                 Width = 40
               end>
               end>
             ColumnClick = False
             ColumnClick = False
@@ -2494,7 +2491,6 @@ object PreferencesDialog: TPreferencesDialog
               end
               end
               item
               item
                 Caption = 'Auto'
                 Caption = 'Auto'
-                Tag = 1
                 Width = 40
                 Width = 40
               end>
               end>
             ColumnClick = False
             ColumnClick = False

+ 43 - 9
source/forms/Properties.cpp

@@ -99,25 +99,36 @@ bool __fastcall TPropertiesDialog::Execute(TRemoteProperties & Properties)
   if (FAllowedChanges & cpMode) ActiveControl = RightsFrame;
   if (FAllowedChanges & cpMode) ActiveControl = RightsFrame;
     else ActiveControl = CancelButton;
     else ActiveControl = CancelButton;
 
 
-  assert(FChecksumAlgs != NULL);
-  ChecksumAlgEdit->Items->Assign(FChecksumAlgs);
-  ChecksumAlgEdit->Text = GUIConfiguration->ChecksumAlg;
+  if (ALWAYS_TRUE(FChecksumAlgs != NULL))
+  {
+    ChecksumAlgEdit->Items->Assign(FChecksumAlgs);
+    int ChecksumIndex = FChecksumAlgs->IndexOf(GUIConfiguration->ChecksumAlg);
+    if (ChecksumIndex < 0)
+    {
+      ChecksumIndex = 0;
+    }
+    ChecksumAlgEdit->ItemIndex = ChecksumIndex;
+  }
   ResetChecksum();
   ResetChecksum();
 
 
   UpdateControls();
   UpdateControls();
 
 
-  bool Result = (ShowModal() == DefaultResult(this));
+  bool Result = (ShowModal() == DefaultResult());
 
 
   if (Result)
   if (Result)
   {
   {
     Properties = GetFileProperties();
     Properties = GetFileProperties();
   }
   }
 
 
-  GUIConfiguration->ChecksumAlg = ChecksumAlgEdit->Text;
-
   return Result;
   return Result;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+TModalResult __fastcall TPropertiesDialog::DefaultResult()
+{
+  // Fallback when ChecksumButton is default
+  return ::DefaultResult(this, OkButton);
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall TPropertiesDialog::LoadRemoteToken(
 UnicodeString __fastcall TPropertiesDialog::LoadRemoteToken(
   const TRemoteToken & Token)
   const TRemoteToken & Token)
 {
 {
@@ -241,7 +252,7 @@ void __fastcall TPropertiesDialog::LoadInfo()
     assert(File && FShellImageList);
     assert(File && FShellImageList);
 
 
     // shell image list does not have fixed large icon size
     // shell image list does not have fixed large icon size
-    // (it is probably 32x32 min, but can be larged, on xp it is 48x48 if
+    // (it is probably 32x32 min, but can be larger, on xp it is 48x48 if
     // large icons are enabled, on vista can be even larger).
     // large icons are enabled, on vista can be even larger).
     // so we stretch (shrink) the icon to 32x32 here to be sure.
     // so we stretch (shrink) the icon to 32x32 here to be sure.
     Graphics::TBitmap * Bitmap = new Graphics::TBitmap;
     Graphics::TBitmap * Bitmap = new Graphics::TBitmap;
@@ -535,12 +546,15 @@ void __fastcall TPropertiesDialog::UpdateControls()
   // hide checksum edit at least if it is disabled to get rid of ugly
   // hide checksum edit at least if it is disabled to get rid of ugly
   // visage on XP
   // visage on XP
   ChecksumEdit->Visible = ChecksumEdit->Enabled;
   ChecksumEdit->Visible = ChecksumEdit->Enabled;
+
+  DefaultButton(ChecksumButton, ChecksumAlgEdit->Focused());
+  DefaultButton(OkButton, !ChecksumAlgEdit->Focused());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TPropertiesDialog::FormCloseQuery(TObject * /*Sender*/,
 void __fastcall TPropertiesDialog::FormCloseQuery(TObject * /*Sender*/,
       bool & /*CanClose*/)
       bool & /*CanClose*/)
 {
 {
-  if (ModalResult == DefaultResult(this))
+  if (ModalResult == DefaultResult())
   {
   {
     ExitActiveControl(this);
     ExitActiveControl(this);
   }
   }
@@ -578,6 +592,7 @@ void __fastcall TPropertiesDialog::ResetChecksum()
 {
 {
   ChecksumView->Items->Clear();
   ChecksumView->Items->Clear();
   ChecksumEdit->Text = LoadStr(PROPERTIES_CHECKSUM_UNKNOWN);
   ChecksumEdit->Text = LoadStr(PROPERTIES_CHECKSUM_UNKNOWN);
+  AutoSizeListColumnsWidth(ChecksumView);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TPropertiesDialog::CalculateChecksum()
 void __fastcall TPropertiesDialog::CalculateChecksum()
@@ -586,6 +601,7 @@ void __fastcall TPropertiesDialog::CalculateChecksum()
 
 
   ResetChecksum();
   ResetChecksum();
   FChecksumLoaded = true;
   FChecksumLoaded = true;
+  FAlgUsed = UnicodeString();
 
 
   bool DoClose = false;
   bool DoClose = false;
   try
   try
@@ -599,10 +615,18 @@ void __fastcall TPropertiesDialog::CalculateChecksum()
       Close();
       Close();
     }
     }
   }
   }
+
+  // If we successfully used the selected checksum, remember it (in normalized form)
+  if (!FAlgUsed.IsEmpty())
+  {
+    GUIConfiguration->ChecksumAlg = FAlgUsed;
+  }
+
+  AutoSizeListColumnsWidth(ChecksumView);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TPropertiesDialog::CalculatedChecksum(
 void __fastcall TPropertiesDialog::CalculatedChecksum(
-  const UnicodeString & FileName, const UnicodeString & /*Alg*/,
+  const UnicodeString & FileName, const UnicodeString & Alg,
   const UnicodeString & Hash)
   const UnicodeString & Hash)
 {
 {
   if (FMultipleChecksum)
   if (FMultipleChecksum)
@@ -610,11 +634,21 @@ void __fastcall TPropertiesDialog::CalculatedChecksum(
     TListItem * Item = ChecksumView->Items->Add();
     TListItem * Item = ChecksumView->Items->Add();
     Item->Caption = FileName;
     Item->Caption = FileName;
     Item->SubItems->Add(Hash);
     Item->SubItems->Add(Hash);
+
+    // optimization
+    int TopIndex = ListView_GetTopIndex(ChecksumView->Handle);
+    int Index = Item->Index;
+    if ((TopIndex <= Index) &&
+        (Index <= TopIndex + ChecksumView->VisibleRowCount))
+    {
+      AutoSizeListColumnsWidth(ChecksumView);
+    }
   }
   }
   else
   else
   {
   {
     ChecksumEdit->Text = Hash;
     ChecksumEdit->Text = Hash;
   }
   }
+  FAlgUsed = Alg;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TPropertiesDialog::NeedChecksum()
 void __fastcall TPropertiesDialog::NeedChecksum()

+ 5 - 7
source/forms/Properties.dfm

@@ -143,7 +143,7 @@ object PropertiesDialog: TPropertiesDialog
         Caption = 'Links to:'
         Caption = 'Links to:'
       end
       end
       object LinksToLabel: TPathLabel
       object LinksToLabel: TPathLabel
-        Left = 88
+        Left = 85
         Top = 102
         Top = 102
         Width = 240
         Width = 240
         Height = 13
         Height = 13
@@ -283,15 +283,11 @@ object PropertiesDialog: TPropertiesDialog
         Columns = <
         Columns = <
           item
           item
             Caption = 'File'
             Caption = 'File'
-            Width = -2
-            WidthType = (
-              -2)
+            Width = 100
           end
           end
           item
           item
             Caption = 'Checksum'
             Caption = 'Checksum'
-            Width = -2
-            WidthType = (
-              -2)
+            Width = 100
           end>
           end>
         ColumnClick = False
         ColumnClick = False
         DoubleBuffered = True
         DoubleBuffered = True
@@ -314,6 +310,8 @@ object PropertiesDialog: TPropertiesDialog
         MaxLength = 250
         MaxLength = 250
         TabOrder = 0
         TabOrder = 0
         OnChange = ChecksumAlgEditChange
         OnChange = ChecksumAlgEditChange
+        OnEnter = ControlChange
+        OnExit = ControlChange
         Items.Strings = (
         Items.Strings = (
           'Xmd5')
           'Xmd5')
       end
       end

+ 2 - 0
source/forms/Properties.h

@@ -87,6 +87,7 @@ private:
   TCalculateSizeEvent FOnCalculateSize;
   TCalculateSizeEvent FOnCalculateSize;
   TCalculateChecksumEvent FOnCalculateChecksum;
   TCalculateChecksumEvent FOnCalculateChecksum;
   bool FChecksumLoaded;
   bool FChecksumLoaded;
+  UnicodeString FAlgUsed;
   bool FMultipleChecksum;
   bool FMultipleChecksum;
 
 
   void __fastcall CalculateChecksum();
   void __fastcall CalculateChecksum();
@@ -97,6 +98,7 @@ private:
     const UnicodeString & FileName, const UnicodeString & Alg, const UnicodeString & Hash);
     const UnicodeString & FileName, const UnicodeString & Alg, const UnicodeString & Hash);
   void __fastcall SetFileProperties(const TRemoteProperties & value);
   void __fastcall SetFileProperties(const TRemoteProperties & value);
   TRemoteProperties __fastcall GetFileProperties();
   TRemoteProperties __fastcall GetFileProperties();
+  TModalResult __fastcall DefaultResult();
 
 
 protected:
 protected:
   void __fastcall LoadInfo();
   void __fastcall LoadInfo();

+ 2 - 0
source/forms/Rights.cpp

@@ -327,6 +327,8 @@ void __fastcall TRightsFrame::RightsActionsExecute(TBasicAction * Action,
       // trigger on change event, even if no change actually occurred to
       // trigger on change event, even if no change actually occurred to
       // allow parent form to visualize feedback of an action
       // allow parent form to visualize feedback of an action
       DoChange();
       DoChange();
+      // Update octal edit in RightsExt even if it has focus
+      ForceUpdate();
       Changed = false;
       Changed = false;
     }
     }
     else
     else

+ 1 - 0
source/forms/Rights.h

@@ -14,6 +14,7 @@
 #include <Menus.hpp>
 #include <Menus.hpp>
 #include "GrayedCheckBox.hpp"
 #include "GrayedCheckBox.hpp"
 #include "PngImageList.hpp"
 #include "PngImageList.hpp"
+#include <System.Actions.hpp>
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TRightsFrame : public TFrame
 class TRightsFrame : public TFrame
 {
 {

+ 8 - 3
source/forms/RightsExt.cpp

@@ -22,7 +22,7 @@ void __fastcall TRightsExtFrame::UpdateControls()
 {
 {
   if (!OctalEdit->Focused())
   if (!OctalEdit->Focused())
   {
   {
-    ForceUpdate();
+    UpdateOctalEdit();
   }
   }
   TRightsFrame::UpdateControls();
   TRightsFrame::UpdateControls();
 }
 }
@@ -39,15 +39,20 @@ void __fastcall TRightsExtFrame::UpdateByOctal()
   OctalEdit->Modified = false;
   OctalEdit->Modified = false;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-void __fastcall TRightsExtFrame::ForceUpdate()
+void __fastcall TRightsExtFrame::UpdateOctalEdit()
 {
 {
-  TRightsFrame::ForceUpdate();
   TRights R = Rights;
   TRights R = Rights;
   OctalEdit->Text = R.IsUndef ? UnicodeString() : R.Octal;
   OctalEdit->Text = R.IsUndef ? UnicodeString() : R.Octal;
   OctalEdit->Modified = false;
   OctalEdit->Modified = false;
   OctalEdit->SelectAll();
   OctalEdit->SelectAll();
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TRightsExtFrame::ForceUpdate()
+{
+  TRightsFrame::ForceUpdate();
+  UpdateOctalEdit();
+}
+//---------------------------------------------------------------------------
 void __fastcall TRightsExtFrame::OctalEditChange(TObject * /*Sender*/)
 void __fastcall TRightsExtFrame::OctalEditChange(TObject * /*Sender*/)
 {
 {
   if (OctalEdit->Modified && OctalEdit->Text.Length() >= 3)
   if (OctalEdit->Modified && OctalEdit->Text.Length() >= 3)

+ 2 - 0
source/forms/RightsExt.h

@@ -13,6 +13,7 @@
 #include <Menus.hpp>
 #include <Menus.hpp>
 #include "GrayedCheckBox.hpp"
 #include "GrayedCheckBox.hpp"
 #include "PngImageList.hpp"
 #include "PngImageList.hpp"
+#include <System.Actions.hpp>
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TRightsExtFrame : public TRightsFrame
 class TRightsExtFrame : public TRightsFrame
 {
 {
@@ -37,6 +38,7 @@ protected:
 
 
 private:
 private:
   void __fastcall UpdateByOctal();
   void __fastcall UpdateByOctal();
+  void __fastcall UpdateOctalEdit();
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #endif
 #endif

+ 8 - 8
source/forms/ScpCommander.cpp

@@ -241,14 +241,12 @@ void __fastcall TScpCommanderForm::UpdateTerminal(TTerminal * Terminal)
   if (WinConfiguration->PreservePanelState)
   if (WinConfiguration->PreservePanelState)
   {
   {
     ManagedTerminal->LocalExplorerState = LocalDirView->SaveState();
     ManagedTerminal->LocalExplorerState = LocalDirView->SaveState();
-    ManagedTerminal->SynchronizeBrowsing = NonVisualDataModule->SynchronizeBrowsingAction->Checked;
   }
   }
-
-  ManagedTerminal->LocalDirectory = LocalDirView->PathName;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::UpdateSessionData(TSessionData * Data)
 void __fastcall TScpCommanderForm::UpdateSessionData(TSessionData * Data)
 {
 {
+  // Keep in sync with TSessionData::CopyStateData
   TCustomScpExplorerForm::UpdateSessionData(Data);
   TCustomScpExplorerForm::UpdateSessionData(Data);
 
 
   assert(LocalDirView);
   assert(LocalDirView);
@@ -299,7 +297,7 @@ bool __fastcall TScpCommanderForm::InternalDDDownload(UnicodeString & TargetDire
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 bool __fastcall TScpCommanderForm::CopyParamDialog(TTransferDirection Direction,
 bool __fastcall TScpCommanderForm::CopyParamDialog(TTransferDirection Direction,
   TTransferType Type, bool Temp, TStrings * FileList, UnicodeString & TargetDirectory,
   TTransferType Type, bool Temp, TStrings * FileList, UnicodeString & TargetDirectory,
-  TGUICopyParamType & CopyParam, bool Confirm, bool DragDrop)
+  TGUICopyParamType & CopyParam, bool Confirm, bool DragDrop, int Options)
 {
 {
   bool Result = false;
   bool Result = false;
   // Temp means d&d here so far, may change in future!
   // Temp means d&d here so far, may change in future!
@@ -328,7 +326,7 @@ bool __fastcall TScpCommanderForm::CopyParamDialog(TTransferDirection Direction,
   if (!Result)
   if (!Result)
   {
   {
     Result = TCustomScpExplorerForm::CopyParamDialog(Direction, Type, Temp,
     Result = TCustomScpExplorerForm::CopyParamDialog(Direction, Type, Temp,
-      FileList, TargetDirectory, CopyParam, Confirm, DragDrop);
+      FileList, TargetDirectory, CopyParam, Confirm, DragDrop, Options);
   }
   }
   return Result;
   return Result;
 }
 }
@@ -347,6 +345,7 @@ Boolean __fastcall TScpCommanderForm::AllowedAction(TAction * Action, TActionAll
 {
 {
   #define FLAG ((TActionFlag)(Action->Tag))
   #define FLAG ((TActionFlag)(Action->Tag))
   return
   return
+    TCustomScpExplorerForm::AllowedAction(Action, Allowed) &&
     // always require Commander flag
     // always require Commander flag
     (FLAG & afCommander) &&
     (FLAG & afCommander) &&
     // if action is execution or update, we don't require any other flag
     // if action is execution or update, we don't require any other flag
@@ -432,7 +431,7 @@ void __fastcall TScpCommanderForm::TerminalChanged()
 
 
     if (FFirstTerminal || !WinConfiguration->ScpCommander.PreserveLocalDirectory)
     if (FFirstTerminal || !WinConfiguration->ScpCommander.PreserveLocalDirectory)
     {
     {
-      UnicodeString LocalDirectory = ManagedTerminal->LocalDirectory;
+      UnicodeString LocalDirectory = ManagedTerminal->StateData->LocalDirectory;
       bool DocumentsDir = LocalDirectory.IsEmpty();
       bool DocumentsDir = LocalDirectory.IsEmpty();
 
 
       if (!DocumentsDir)
       if (!DocumentsDir)
@@ -482,7 +481,7 @@ void __fastcall TScpCommanderForm::TerminalChanged()
         LocalDirView->ClearState();
         LocalDirView->ClearState();
       }
       }
 
 
-      NonVisualDataModule->SynchronizeBrowsingAction->Checked = ManagedTerminal->SynchronizeBrowsing;
+      NonVisualDataModule->SynchronizeBrowsingAction->Checked = ManagedTerminal->StateData->SynchronizeBrowsing;
     }
     }
   }
   }
   else
   else
@@ -1394,9 +1393,10 @@ void __fastcall TScpCommanderForm::LocalFileControlDDFileOperation(
         {
         {
           TransferType = ttMove;
           TransferType = ttMove;
         }
         }
+        int Options = 0;
         if (CopyParamDialog(tdToLocal, TransferType,
         if (CopyParamDialog(tdToLocal, TransferType,
               false, FInternalDDDownloadList, TargetDirectory, CopyParams,
               false, FInternalDDDownloadList, TargetDirectory, CopyParams,
-              (WinConfiguration->DDTransferConfirmation != asOff), true))
+              (WinConfiguration->DDTransferConfirmation != asOff), true, Options))
         {
         {
           int Params =
           int Params =
             (TransferType == ttMove ? cpDelete : 0);
             (TransferType == ttMove ? cpDelete : 0);

+ 4 - 0
source/forms/ScpCommander.dfm

@@ -340,6 +340,9 @@ inherited ScpCommanderForm: TScpCommanderForm
             object TBXItem199: TTBXItem
             object TBXItem199: TTBXItem
               Action = NonVisualDataModule.QueueResumeAllAction
               Action = NonVisualDataModule.QueueResumeAllAction
             end
             end
+            object TBXItem142: TTBXItem
+              Action = NonVisualDataModule.QueueDeleteAllAction
+            end
             object TBXSeparatorItem39: TTBXSeparatorItem
             object TBXSeparatorItem39: TTBXSeparatorItem
             end
             end
             object TBXItem134: TTBXItem
             object TBXItem134: TTBXItem
@@ -945,6 +948,7 @@ inherited ScpCommanderForm: TScpCommanderForm
           MinWidth = 350
           MinWidth = 350
         end
         end
         object TransferLabel: TTBXLabelItem
         object TransferLabel: TTBXLabelItem
+          Caption = ''
           Margin = 4
           Margin = 4
           ShowAccelChar = False
           ShowAccelChar = False
         end
         end

+ 2 - 1
source/forms/ScpCommander.h

@@ -401,6 +401,7 @@ __published:
   TTBXSeparatorItem *TBXSeparatorItem50;
   TTBXSeparatorItem *TBXSeparatorItem50;
   TTBXItem *TBXItem135;
   TTBXItem *TBXItem135;
   TTBXItem *TBXItem141;
   TTBXItem *TBXItem141;
+  TTBXItem *TBXItem142;
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
     bool &Accept);
     bool &Accept);
@@ -493,7 +494,7 @@ protected:
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool Temp, TStrings * FileList,
     TTransferType Type, bool Temp, TStrings * FileList,
     UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
     UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
-    bool DragDrop);
+    bool DragDrop, int Options);
   virtual TCustomDirView * __fastcall DirView(TOperationSide Side);
   virtual TCustomDirView * __fastcall DirView(TOperationSide Side);
   TControl * __fastcall GetComponent(Byte Component);
   TControl * __fastcall GetComponent(Byte Component);
   virtual void __fastcall RestoreFormParams();
   virtual void __fastcall RestoreFormParams();

+ 4 - 2
source/forms/ScpExplorer.cpp

@@ -134,7 +134,7 @@ void __fastcall TScpExplorerForm::StoreParams()
 bool __fastcall TScpExplorerForm::CopyParamDialog(TTransferDirection Direction,
 bool __fastcall TScpExplorerForm::CopyParamDialog(TTransferDirection Direction,
   TTransferType Type, Boolean Temp, TStrings * FileList,
   TTransferType Type, Boolean Temp, TStrings * FileList,
   UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
   UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
-  bool DragDrop)
+  bool DragDrop, int Options)
 {
 {
   // Temp means d&d here so far, may change in future!
   // Temp means d&d here so far, may change in future!
   if ((Direction == tdToLocal) && !Temp && TargetDirectory.IsEmpty())
   if ((Direction == tdToLocal) && !Temp && TargetDirectory.IsEmpty())
@@ -142,7 +142,8 @@ bool __fastcall TScpExplorerForm::CopyParamDialog(TTransferDirection Direction,
     TargetDirectory = WinConfiguration->ScpExplorer.LastLocalTargetDirectory;
     TargetDirectory = WinConfiguration->ScpExplorer.LastLocalTargetDirectory;
   }
   }
   bool Result = TCustomScpExplorerForm::CopyParamDialog(
   bool Result = TCustomScpExplorerForm::CopyParamDialog(
-    Direction, Type, Temp, FileList, TargetDirectory, CopyParam, Confirm, DragDrop);
+    Direction, Type, Temp, FileList, TargetDirectory, CopyParam, Confirm,
+    DragDrop, Options);
   if (Result && (Direction == tdToLocal) && !Temp)
   if (Result && (Direction == tdToLocal) && !Temp)
   {
   {
     WinConfiguration->ScpExplorer.LastLocalTargetDirectory = TargetDirectory;
     WinConfiguration->ScpExplorer.LastLocalTargetDirectory = TargetDirectory;
@@ -168,6 +169,7 @@ bool __fastcall TScpExplorerForm::AllowedAction(TAction * Action, TActionAllowed
   }
   }
   #define FLAG ((TActionFlag)(Action->Tag))
   #define FLAG ((TActionFlag)(Action->Tag))
   return
   return
+    TCustomScpExplorerForm::AllowedAction(Action, Allowed) &&
     // always require Explorer flag
     // always require Explorer flag
     (FLAG & afExplorer) &&
     (FLAG & afExplorer) &&
     // if action is execution or update, we don't require any other flag
     // if action is execution or update, we don't require any other flag

+ 4 - 0
source/forms/ScpExplorer.dfm

@@ -187,6 +187,9 @@ inherited ScpExplorerForm: TScpExplorerForm
             object TBXItem199: TTBXItem
             object TBXItem199: TTBXItem
               Action = NonVisualDataModule.QueueResumeAllAction
               Action = NonVisualDataModule.QueueResumeAllAction
             end
             end
+            object TBXItem154: TTBXItem
+              Action = NonVisualDataModule.QueueDeleteAllAction
+            end
             object TBXSeparatorItem35: TTBXSeparatorItem
             object TBXSeparatorItem35: TTBXSeparatorItem
             end
             end
             object TBXItem143: TTBXItem
             object TBXItem143: TTBXItem
@@ -905,6 +908,7 @@ inherited ScpExplorerForm: TScpExplorerForm
           MinWidth = 350
           MinWidth = 350
         end
         end
         object TransferLabel: TTBXLabelItem
         object TransferLabel: TTBXLabelItem
+          Caption = ''
           Margin = 4
           Margin = 4
           ShowAccelChar = False
           ShowAccelChar = False
         end
         end

+ 2 - 1
source/forms/ScpExplorer.h

@@ -296,6 +296,7 @@ __published:
   TTBXSeparatorItem *TBXSeparatorItem37;
   TTBXSeparatorItem *TBXSeparatorItem37;
   TTBXItem *TBXItem144;
   TTBXItem *TBXItem144;
   TTBXItem *TBXItem148;
   TTBXItem *TBXItem148;
+  TTBXItem *TBXItem154;
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
           const TStatusFileInfo &FileInfo);
           const TStatusFileInfo &FileInfo);
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,
@@ -316,7 +317,7 @@ protected:
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool Temp, TStrings * FileList,
     TTransferType Type, bool Temp, TStrings * FileList,
     UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
     UnicodeString & TargetDirectory, TGUICopyParamType & CopyParam, bool Confirm,
-    bool DragDrop);
+    bool DragDrop, int Options);
   virtual void __fastcall RestoreFormParams();
   virtual void __fastcall RestoreFormParams();
   virtual void __fastcall RestoreParams();
   virtual void __fastcall RestoreParams();
   virtual void __fastcall ConfigurationChanged();
   virtual void __fastcall ConfigurationChanged();

+ 3 - 0
source/forms/SiteAdvanced.cpp

@@ -80,6 +80,7 @@ void __fastcall TSiteAdvancedDialog::InitControls()
   ComboAutoSwitchInitialize(FtpListAllCombo);
   ComboAutoSwitchInitialize(FtpListAllCombo);
   ComboAutoSwitchInitialize(FtpUseMlsdCombo);
   ComboAutoSwitchInitialize(FtpUseMlsdCombo);
   ComboAutoSwitchInitialize(FtpForcePasvIpCombo);
   ComboAutoSwitchInitialize(FtpForcePasvIpCombo);
+  ComboAutoSwitchInitialize(FtpHostCombo);
 
 
   TunnelLocalPortNumberEdit->Items->BeginUpdate();
   TunnelLocalPortNumberEdit->Items->BeginUpdate();
   try
   try
@@ -213,6 +214,7 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
     ComboAutoSwitchLoad(FtpListAllCombo, FSessionData->FtpListAll);
     ComboAutoSwitchLoad(FtpListAllCombo, FSessionData->FtpListAll);
     ComboAutoSwitchLoad(FtpUseMlsdCombo, FSessionData->FtpUseMlsd);
     ComboAutoSwitchLoad(FtpUseMlsdCombo, FSessionData->FtpUseMlsd);
     ComboAutoSwitchLoad(FtpForcePasvIpCombo, FSessionData->FtpForcePasvIp);
     ComboAutoSwitchLoad(FtpForcePasvIpCombo, FSessionData->FtpForcePasvIp);
+    ComboAutoSwitchLoad(FtpHostCombo, FSessionData->FtpHost);
 
 
     // Authentication page
     // Authentication page
     SshNoUserAuthCheck->Checked = FSessionData->SshNoUserAuth;
     SshNoUserAuthCheck->Checked = FSessionData->SshNoUserAuth;
@@ -598,6 +600,7 @@ void __fastcall TSiteAdvancedDialog::SaveSession()
   FSessionData->FtpListAll = ComboAutoSwitchSave(FtpListAllCombo);
   FSessionData->FtpListAll = ComboAutoSwitchSave(FtpListAllCombo);
   FSessionData->FtpUseMlsd = ComboAutoSwitchSave(FtpUseMlsdCombo);
   FSessionData->FtpUseMlsd = ComboAutoSwitchSave(FtpUseMlsdCombo);
   FSessionData->FtpForcePasvIp = ComboAutoSwitchSave(FtpForcePasvIpCombo);
   FSessionData->FtpForcePasvIp = ComboAutoSwitchSave(FtpForcePasvIpCombo);
+  FSessionData->FtpHost = ComboAutoSwitchSave(FtpHostCombo);
 
 
   // Proxy page
   // Proxy page
   FSessionData->ProxyMethod = GetProxyMethod();
   FSessionData->ProxyMethod = GetProxyMethod();

+ 20 - 2
source/forms/SiteAdvanced.dfm

@@ -721,13 +721,13 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
           Left = 0
           Left = 0
           Top = 6
           Top = 6
           Width = 393
           Width = 393
-          Height = 200
+          Height = 224
           Anchors = [akLeft, akTop, akRight]
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Protocol options'
           Caption = 'Protocol options'
           TabOrder = 0
           TabOrder = 0
           DesignSize = (
           DesignSize = (
             393
             393
-            200)
+            224)
           object Label25: TLabel
           object Label25: TLabel
             Left = 12
             Left = 12
             Top = 42
             Top = 42
@@ -768,6 +768,14 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
             Caption = '&Account:'
             Caption = '&Account:'
             FocusControl = FtpAccountEdit
             FocusControl = FtpAccountEdit
           end
           end
+          object Label3: TLabel
+            Left = 12
+            Top = 196
+            Width = 232
+            Height = 13
+            Caption = 'Use &HOST command to select host on the server'
+            FocusControl = FtpHostCombo
+          end
           object PostLoginCommandsMemo: TMemo
           object PostLoginCommandsMemo: TMemo
             Left = 12
             Left = 12
             Top = 59
             Top = 59
@@ -818,6 +826,16 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
             Text = 'FtpAccountEdit'
             Text = 'FtpAccountEdit'
             OnChange = DataChange
             OnChange = DataChange
           end
           end
+          object FtpHostCombo: TComboBox
+            Left = 320
+            Top = 191
+            Width = 61
+            Height = 21
+            Style = csDropDownList
+            Anchors = [akTop, akRight]
+            TabOrder = 5
+            OnChange = DataChange
+          end
         end
         end
       end
       end
       object ConnSheet: TTabSheet
       object ConnSheet: TTabSheet

+ 2 - 0
source/forms/SiteAdvanced.h

@@ -250,6 +250,8 @@ __published:
   TGroupBox *NoteGroup;
   TGroupBox *NoteGroup;
   TMemo *NoteMemo;
   TMemo *NoteMemo;
   TCheckBox *TimeDifferenceAutoCheck;
   TCheckBox *TimeDifferenceAutoCheck;
+  TLabel *Label3;
+  TComboBox *FtpHostCombo;
   void __fastcall DataChange(TObject *Sender);
   void __fastcall DataChange(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall PageControlChange(TObject *Sender);
   void __fastcall PageControlChange(TObject *Sender);

+ 27 - 2
source/forms/Synchronize.cpp

@@ -15,7 +15,7 @@
 #include <Configuration.h>
 #include <Configuration.h>
 #include <TextsWin.h>
 #include <TextsWin.h>
 #include <HelpWin.h>
 #include <HelpWin.h>
-#include <CustomWinConfiguration.h>
+#include <WinConfiguration.h>
 #include <StrUtils.hpp>
 #include <StrUtils.hpp>
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 #pragma package(smart_init)
@@ -175,6 +175,10 @@ void __fastcall TSynchronizeDialog::UpdateControls()
       LogPanel->Visible = false;
       LogPanel->Visible = false;
     }
     }
   }
   }
+
+  // When minimizing to tray globally, no point showing special "minimize to tray" command
+  MinimizeButton->Style =
+    !WinConfiguration->MinimizeToTray ? TCustomButton::bsSplitButton : TCustomButton::bsPushButton;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TSynchronizeDialog::ControlChange(TObject * /*Sender*/)
 void __fastcall TSynchronizeDialog::ControlChange(TObject * /*Sender*/)
@@ -468,11 +472,16 @@ void __fastcall TSynchronizeDialog::Stop()
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-void __fastcall TSynchronizeDialog::MinimizeButtonClick(TObject * Sender)
+void __fastcall TSynchronizeDialog::Minimize(TObject * Sender)
 {
 {
   CallGlobalMinimizeHandler(Sender);
   CallGlobalMinimizeHandler(Sender);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TSynchronizeDialog::MinimizeButtonClick(TObject * Sender)
+{
+  Minimize(Sender);
+}
+//---------------------------------------------------------------------------
 void __fastcall TSynchronizeDialog::GlobalMinimize(TObject * /*Sender*/)
 void __fastcall TSynchronizeDialog::GlobalMinimize(TObject * /*Sender*/)
 {
 {
   ApplicationMinimize();
   ApplicationMinimize();
@@ -664,3 +673,19 @@ void __fastcall TSynchronizeDialog::LogViewDblClick(TObject * /*Sender*/)
   }
   }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+void __fastcall TSynchronizeDialog::Minimize1Click(TObject * Sender)
+{
+  Minimize(Sender);
+}
+//---------------------------------------------------------------------------
+void __fastcall TSynchronizeDialog::MinimizetoTray1Click(TObject * Sender)
+{
+  WinConfiguration->MinimizeToTrayOnce();
+  Minimize(Sender);
+}
+//---------------------------------------------------------------------------
+void __fastcall TSynchronizeDialog::MinimizeButtonDropDownClick(TObject * /*Sender*/)
+{
+  MenuPopup(MinimizeMenu, MinimizeButton);
+}
+//---------------------------------------------------------------------------

+ 47 - 33
source/forms/Synchronize.dfm

@@ -7,7 +7,7 @@ object SynchronizeDialog: TSynchronizeDialog
   BorderStyle = bsDialog
   BorderStyle = bsDialog
   Caption = 'Keep remote directory up to date X'
   Caption = 'Keep remote directory up to date X'
   ClientHeight = 445
   ClientHeight = 445
-  ClientWidth = 436
+  ClientWidth = 468
   Color = clBtnFace
   Color = clBtnFace
   ParentFont = True
   ParentFont = True
   KeyPreview = True
   KeyPreview = True
@@ -17,25 +17,25 @@ object SynchronizeDialog: TSynchronizeDialog
   OnKeyDown = FormKeyDown
   OnKeyDown = FormKeyDown
   OnShow = FormShow
   OnShow = FormShow
   DesignSize = (
   DesignSize = (
-    436
+    468
     445)
     445)
   PixelsPerInch = 96
   PixelsPerInch = 96
   TextHeight = 13
   TextHeight = 13
   object DirectoriesGroup: TGroupBox
   object DirectoriesGroup: TGroupBox
     Left = 8
     Left = 8
     Top = 6
     Top = 6
-    Width = 421
+    Width = 453
     Height = 119
     Height = 119
     Anchors = [akLeft, akTop, akRight]
     Anchors = [akLeft, akTop, akRight]
     Caption = 'Directories'
     Caption = 'Directories'
     TabOrder = 0
     TabOrder = 0
     DesignSize = (
     DesignSize = (
-      421
+      453
       119)
       119)
     object LocalDirectoryLabel: TLabel
     object LocalDirectoryLabel: TLabel
       Left = 49
       Left = 49
       Top = 19
       Top = 19
-      Width = 195
+      Width = 227
       Height = 13
       Height = 13
       Anchors = [akLeft, akTop, akRight]
       Anchors = [akLeft, akTop, akRight]
       Caption = '&Watch for changes in the local directory:'
       Caption = '&Watch for changes in the local directory:'
@@ -44,7 +44,7 @@ object SynchronizeDialog: TSynchronizeDialog
     object RemoteDirectoryLabel: TLabel
     object RemoteDirectoryLabel: TLabel
       Left = 49
       Left = 49
       Top = 68
       Top = 68
-      Width = 281
+      Width = 313
       Height = 13
       Height = 13
       Anchors = [akLeft, akTop, akRight]
       Anchors = [akLeft, akTop, akRight]
       Caption = '... &and automatically reflect them on the remote directory:'
       Caption = '... &and automatically reflect them on the remote directory:'
@@ -104,7 +104,7 @@ object SynchronizeDialog: TSynchronizeDialog
     object RemoteDirectoryEdit: THistoryComboBox
     object RemoteDirectoryEdit: THistoryComboBox
       Left = 49
       Left = 49
       Top = 84
       Top = 84
-      Width = 361
+      Width = 393
       Height = 21
       Height = 21
       AutoComplete = False
       AutoComplete = False
       Anchors = [akLeft, akTop, akRight]
       Anchors = [akLeft, akTop, akRight]
@@ -116,7 +116,7 @@ object SynchronizeDialog: TSynchronizeDialog
     object LocalDirectoryEdit: THistoryComboBox
     object LocalDirectoryEdit: THistoryComboBox
       Left = 49
       Left = 49
       Top = 35
       Top = 35
-      Width = 280
+      Width = 312
       Height = 21
       Height = 21
       AutoComplete = False
       AutoComplete = False
       Anchors = [akLeft, akTop, akRight]
       Anchors = [akLeft, akTop, akRight]
@@ -126,7 +126,7 @@ object SynchronizeDialog: TSynchronizeDialog
       OnChange = ControlChange
       OnChange = ControlChange
     end
     end
     object LocalDirectoryBrowseButton: TButton
     object LocalDirectoryBrowseButton: TButton
-      Left = 335
+      Left = 367
       Top = 33
       Top = 33
       Width = 75
       Width = 75
       Height = 25
       Height = 25
@@ -137,9 +137,9 @@ object SynchronizeDialog: TSynchronizeDialog
     end
     end
   end
   end
   object StopButton: TButton
   object StopButton: TButton
-    Left = 192
+    Left = 183
     Top = 312
     Top = 312
-    Width = 74
+    Width = 88
     Height = 25
     Height = 25
     Anchors = [akTop, akRight]
     Anchors = [akTop, akRight]
     Caption = '&Stop'
     Caption = '&Stop'
@@ -147,9 +147,9 @@ object SynchronizeDialog: TSynchronizeDialog
     OnClick = StopButtonClick
     OnClick = StopButtonClick
   end
   end
   object CancelButton: TButton
   object CancelButton: TButton
-    Left = 272
+    Left = 277
     Top = 312
     Top = 312
-    Width = 74
+    Width = 88
     Height = 25
     Height = 25
     Anchors = [akTop, akRight]
     Anchors = [akTop, akRight]
     Cancel = True
     Cancel = True
@@ -160,13 +160,13 @@ object SynchronizeDialog: TSynchronizeDialog
   object OptionsGroup: TGroupBox
   object OptionsGroup: TGroupBox
     Left = 8
     Left = 8
     Top = 130
     Top = 130
-    Width = 421
+    Width = 453
     Height = 119
     Height = 119
     Anchors = [akLeft, akTop, akRight]
     Anchors = [akLeft, akTop, akRight]
     Caption = 'Synchronize options'
     Caption = 'Synchronize options'
     TabOrder = 1
     TabOrder = 1
     DesignSize = (
     DesignSize = (
-      421
+      453
       119)
       119)
     object SynchronizeDeleteCheck: TCheckBox
     object SynchronizeDeleteCheck: TCheckBox
       Left = 11
       Left = 11
@@ -187,9 +187,9 @@ object SynchronizeDialog: TSynchronizeDialog
       OnClick = ControlChange
       OnClick = ControlChange
     end
     end
     object SynchronizeExistingOnlyCheck: TCheckBox
     object SynchronizeExistingOnlyCheck: TCheckBox
-      Left = 213
+      Left = 229
       Top = 20
       Top = 20
-      Width = 197
+      Width = 213
       Height = 17
       Height = 17
       Anchors = [akLeft, akTop, akRight]
       Anchors = [akLeft, akTop, akRight]
       Caption = '&Existing files only'
       Caption = '&Existing files only'
@@ -206,9 +206,9 @@ object SynchronizeDialog: TSynchronizeDialog
       OnClick = ControlChange
       OnClick = ControlChange
     end
     end
     object SynchronizeSynchronizeCheck: TGrayedCheckBox
     object SynchronizeSynchronizeCheck: TGrayedCheckBox
-      Left = 213
+      Left = 229
       Top = 68
       Top = 68
-      Width = 197
+      Width = 213
       Height = 17
       Height = 17
       AllowGrayed = True
       AllowGrayed = True
       Anchors = [akLeft, akTop, akRight]
       Anchors = [akLeft, akTop, akRight]
@@ -217,9 +217,9 @@ object SynchronizeDialog: TSynchronizeDialog
       OnClick = ControlChange
       OnClick = ControlChange
     end
     end
     object SynchronizeSelectedOnlyCheck: TCheckBox
     object SynchronizeSelectedOnlyCheck: TCheckBox
-      Left = 213
+      Left = 229
       Top = 44
       Top = 44
-      Width = 197
+      Width = 213
       Height = 17
       Height = 17
       Caption = 'Selected files o&nly'
       Caption = 'Selected files o&nly'
       TabOrder = 3
       TabOrder = 3
@@ -236,9 +236,9 @@ object SynchronizeDialog: TSynchronizeDialog
     end
     end
   end
   end
   object StartButton: TButton
   object StartButton: TButton
-    Left = 192
+    Left = 183
     Top = 312
     Top = 312
-    Width = 74
+    Width = 88
     Height = 25
     Height = 25
     Anchors = [akTop, akRight]
     Anchors = [akTop, akRight]
     Caption = '&Start'
     Caption = '&Start'
@@ -247,14 +247,15 @@ object SynchronizeDialog: TSynchronizeDialog
     OnClick = StartButtonClick
     OnClick = StartButtonClick
   end
   end
   object MinimizeButton: TButton
   object MinimizeButton: TButton
-    Left = 273
+    Left = 277
     Top = 312
     Top = 312
-    Width = 74
+    Width = 88
     Height = 25
     Height = 25
     Anchors = [akTop, akRight]
     Anchors = [akTop, akRight]
     Caption = '&Minimize'
     Caption = '&Minimize'
     TabOrder = 5
     TabOrder = 5
     OnClick = MinimizeButtonClick
     OnClick = MinimizeButtonClick
+    OnDropDownClick = MinimizeButtonDropDownClick
   end
   end
   object TransferSettingsButton: TButton
   object TransferSettingsButton: TButton
     Left = 8
     Left = 8
@@ -269,7 +270,7 @@ object SynchronizeDialog: TSynchronizeDialog
   object CopyParamGroup: TGroupBox
   object CopyParamGroup: TGroupBox
     Left = 8
     Left = 8
     Top = 254
     Top = 254
-    Width = 421
+    Width = 453
     Height = 50
     Height = 50
     Anchors = [akLeft, akTop, akRight]
     Anchors = [akLeft, akTop, akRight]
     Caption = 'Transfer settings'
     Caption = 'Transfer settings'
@@ -277,12 +278,12 @@ object SynchronizeDialog: TSynchronizeDialog
     OnClick = CopyParamGroupClick
     OnClick = CopyParamGroupClick
     OnContextPopup = CopyParamGroupContextPopup
     OnContextPopup = CopyParamGroupContextPopup
     DesignSize = (
     DesignSize = (
-      421
+      453
       50)
       50)
     object CopyParamLabel: TLabel
     object CopyParamLabel: TLabel
       Left = 7
       Left = 7
       Top = 15
       Top = 15
-      Width = 407
+      Width = 439
       Height = 26
       Height = 26
       Anchors = [akLeft, akTop, akRight, akBottom]
       Anchors = [akLeft, akTop, akRight, akBottom]
       AutoSize = False
       AutoSize = False
@@ -292,9 +293,9 @@ object SynchronizeDialog: TSynchronizeDialog
     end
     end
   end
   end
   object HelpButton: TButton
   object HelpButton: TButton
-    Left = 353
+    Left = 371
     Top = 312
     Top = 312
-    Width = 75
+    Width = 88
     Height = 25
     Height = 25
     Anchors = [akTop, akRight]
     Anchors = [akTop, akRight]
     Caption = '&Help'
     Caption = '&Help'
@@ -304,18 +305,18 @@ object SynchronizeDialog: TSynchronizeDialog
   object LogPanel: TPanel
   object LogPanel: TPanel
     Left = 0
     Left = 0
     Top = 345
     Top = 345
-    Width = 436
+    Width = 468
     Height = 100
     Height = 100
     Align = alBottom
     Align = alBottom
     BevelOuter = bvNone
     BevelOuter = bvNone
     TabOrder = 9
     TabOrder = 9
     DesignSize = (
     DesignSize = (
-      436
+      468
       100)
       100)
     object LogView: TListView
     object LogView: TListView
       Left = 8
       Left = 8
       Top = 2
       Top = 2
-      Width = 420
+      Width = 452
       Height = 90
       Height = 90
       Anchors = [akLeft, akTop, akRight, akBottom]
       Anchors = [akLeft, akTop, akRight, akBottom]
       Columns = <
       Columns = <
@@ -342,4 +343,17 @@ object SynchronizeDialog: TSynchronizeDialog
       OnKeyDown = LogViewKeyDown
       OnKeyDown = LogViewKeyDown
     end
     end
   end
   end
+  object MinimizeMenu: TPopupMenu
+    Left = 376
+    Top = 376
+    object Minimize1: TMenuItem
+      Caption = '&Minimize'
+      Default = True
+      OnClick = Minimize1Click
+    end
+    object MinimizetoTray1: TMenuItem
+      Caption = 'Minimize to System &Tray'
+      OnClick = MinimizetoTray1Click
+    end
+  end
 end
 end

+ 8 - 0
source/forms/Synchronize.h

@@ -13,6 +13,7 @@
 #include <ComCtrls.hpp>
 #include <ComCtrls.hpp>
 #include <ExtCtrls.hpp>
 #include <ExtCtrls.hpp>
 #include <Vcl.Imaging.pngimage.hpp>
 #include <Vcl.Imaging.pngimage.hpp>
+#include <Vcl.Menus.hpp>
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 struct TLogItemData;
 struct TLogItemData;
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -44,6 +45,9 @@ __published:
   TListView *LogView;
   TListView *LogView;
   TImage *Image;
   TImage *Image;
   TCheckBox *ContinueOnErrorCheck;
   TCheckBox *ContinueOnErrorCheck;
+  TPopupMenu *MinimizeMenu;
+  TMenuItem *Minimize1;
+  TMenuItem *MinimizetoTray1;
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall LocalDirectoryBrowseButtonClick(TObject *Sender);
   void __fastcall LocalDirectoryBrowseButtonClick(TObject *Sender);
   void __fastcall TransferSettingsButtonClick(TObject *Sender);
   void __fastcall TransferSettingsButtonClick(TObject *Sender);
@@ -65,6 +69,9 @@ __published:
           TCustomDrawState State, bool &DefaultDraw);
           TCustomDrawState State, bool &DefaultDraw);
   void __fastcall LogViewDeletion(TObject *Sender, TListItem *Item);
   void __fastcall LogViewDeletion(TObject *Sender, TListItem *Item);
   void __fastcall LogViewDblClick(TObject *Sender);
   void __fastcall LogViewDblClick(TObject *Sender);
+  void __fastcall Minimize1Click(TObject *Sender);
+  void __fastcall MinimizetoTray1Click(TObject *Sender);
+  void __fastcall MinimizeButtonDropDownClick(TObject *Sender);
 
 
 private:
 private:
   TSynchronizeParamType FParams;
   TSynchronizeParamType FParams;
@@ -112,6 +119,7 @@ protected:
     const UnicodeString & Message, TStrings * MoreMessages, TQueryType Type,
     const UnicodeString & Message, TStrings * MoreMessages, TQueryType Type,
     const UnicodeString & HelpKeyword);
     const UnicodeString & HelpKeyword);
   TLogItemData * __fastcall GetLogItemData(TListItem * Item);
   TLogItemData * __fastcall GetLogItemData(TListItem * Item);
+  void __fastcall Minimize(TObject * Sender);
 
 
 public:
 public:
   __fastcall TSynchronizeDialog(TComponent * Owner);
   __fastcall TSynchronizeDialog(TComponent * Owner);

+ 8 - 1
source/packages/filemng/CustomDirView.pas

@@ -2629,7 +2629,14 @@ begin
         {Execute the drag&drop-Operation:}
         {Execute the drag&drop-Operation:}
         FLastDDResult := DragDropFilesEx.Execute(DataObject);
         FLastDDResult := DragDropFilesEx.Execute(DataObject);
 
 
-        {the drag&drop operation is finished, so clean up the used drag image:}
+        // The drag&drop operation is finished, so clean up the used drag image.
+        // This also restores the default mouse cursor
+        // (which is set to "none" in GlobalDragImageList.BeginDrag above)
+        // But it's actually too late, we would need to do it when mouse button
+        // is realesed already. Otherwise the cursor is hidden when hovering over
+        // main window, while target application is processing dropped file
+        // (particularly when Explorer displays progress window or
+        // overwrite confirmation prompt)
         GlobalDragImageList.EndDrag;
         GlobalDragImageList.EndDrag;
         GlobalDragImageList.Clear;
         GlobalDragImageList.Clear;
 
 

+ 4 - 1
source/resource/TextsCore.h

@@ -30,6 +30,7 @@
 #define SCRIPT_CALL_HELP2       26
 #define SCRIPT_CALL_HELP2       26
 #define SCRIPT_ECHO_HELP        27
 #define SCRIPT_ECHO_HELP        27
 #define SCRIPT_STAT_HELP        28
 #define SCRIPT_STAT_HELP        28
+#define SCRIPT_CHECKSUM_HELP    29
 
 
 #define CORE_ERROR_STRINGS      100
 #define CORE_ERROR_STRINGS      100
 #define KEY_NOT_VERIFIED        101
 #define KEY_NOT_VERIFIED        101
@@ -181,7 +182,7 @@
 #define NOTSUPPORTED            255
 #define NOTSUPPORTED            255
 #define FTP_ACCESS_DENIED       256
 #define FTP_ACCESS_DENIED       256
 #define FTP_CREDENTIAL_PROMPT   257
 #define FTP_CREDENTIAL_PROMPT   257
-#define FTP_PWD_RESPONSE_ERROR  258
+#define FTP_RESPONSE_ERROR      258
 #define FTP_UNSUPPORTED         259
 #define FTP_UNSUPPORTED         259
 #define SCRIPT_UNKNOWN_SWITCH   260
 #define SCRIPT_UNKNOWN_SWITCH   260
 #define TRANSFER_ERROR          261
 #define TRANSFER_ERROR          261
@@ -236,6 +237,7 @@
 #define SFTP_STATUS_GROUP_INVALID 712
 #define SFTP_STATUS_GROUP_INVALID 712
 #define SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK 713
 #define SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK 713
 #define KEY_TYPE_UNOPENABLE     714
 #define KEY_TYPE_UNOPENABLE     714
+#define UNKNOWN_CHECKSUM        715
 
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301
 #define CONFIRM_PROLONG_TIMEOUT3 301
@@ -416,6 +418,7 @@
 #define VERSION_DONT_DISTRIBUTE 538
 #define VERSION_DONT_DISTRIBUTE 538
 #define WEBDAV_EXTENSION_INFO   539
 #define WEBDAV_EXTENSION_INFO   539
 #define COPY_PARAM_PRESET_EXCLUDE_ALL_DIR 540
 #define COPY_PARAM_PRESET_EXCLUDE_ALL_DIR 540
+#define SCRIPT_CHECKSUM_DESC    541
 
 
 #define CORE_VARIABLE_STRINGS   600
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601
 #define PUTTY_BASED_ON          601

+ 3 - 1
source/resource/TextsCore1.rc

@@ -152,7 +152,7 @@ BEGIN
   NOTSUPPORTED, "Operation not supported."
   NOTSUPPORTED, "Operation not supported."
   FTP_ACCESS_DENIED, "Access denied."
   FTP_ACCESS_DENIED, "Access denied."
   FTP_CREDENTIAL_PROMPT, "Prompting for credentials..."
   FTP_CREDENTIAL_PROMPT, "Prompting for credentials..."
-  FTP_PWD_RESPONSE_ERROR, "Invalid response to PWD command '%s'."
+  FTP_RESPONSE_ERROR, "Invalid response to %s command '%s'."
   FTP_UNSUPPORTED, "This version does not support FTP protocol."
   FTP_UNSUPPORTED, "This version does not support FTP protocol."
   SCRIPT_UNKNOWN_SWITCH, "Unknown switch '%s'."
   SCRIPT_UNKNOWN_SWITCH, "Unknown switch '%s'."
   TRANSFER_ERROR, "Error transferring file '%s'."
   TRANSFER_ERROR, "Error transferring file '%s'."
@@ -207,6 +207,7 @@ BEGIN
   SFTP_STATUS_GROUP_INVALID, "The name specified can not be assigned as the primary group of a file."
   SFTP_STATUS_GROUP_INVALID, "The name specified can not be assigned as the primary group of a file."
   SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK, "The requested operation could not be completed because the specifed byte range lock has not been granted."
   SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK, "The requested operation could not be completed because the specifed byte range lock has not been granted."
   KEY_TYPE_UNOPENABLE, "Private key file '%s' does not exist or cannot be opened."
   KEY_TYPE_UNOPENABLE, "Private key file '%s' does not exist or cannot be opened."
+  UNKNOWN_CHECKSUM, "Checksum algorithm '%s' is not supported."
 
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"
@@ -387,6 +388,7 @@ BEGIN
   VERSION_DONT_DISTRIBUTE, "- Do NOT distribute"
   VERSION_DONT_DISTRIBUTE, "- Do NOT distribute"
   WEBDAV_EXTENSION_INFO, "The server supports these WebDAV extensions:"
   WEBDAV_EXTENSION_INFO, "The server supports these WebDAV extensions:"
   COPY_PARAM_PRESET_EXCLUDE_ALL_DIR, "Exclude &directories" 
   COPY_PARAM_PRESET_EXCLUDE_ALL_DIR, "Exclude &directories" 
+  SCRIPT_CHECKSUM_DESC, "Calculates checksum of remote file"
 
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 5 - 0
source/resource/TextsCore2.rc

@@ -367,4 +367,9 @@ BEGIN
     "  Retrieves and lists attributes of specified remote file.\n"
     "  Retrieves and lists attributes of specified remote file.\n"
     "example:\n"
     "example:\n"
     "  stat index.html\n"
     "  stat index.html\n"
+  SCRIPT_CHECKSUM_HELP,
+    "checksum <alg> <file>\n"
+    "  Calculates checksum of remote file.\n"
+    "example:\n"
+    "  checksum sha1 index.html\n"
 END
 END

+ 1 - 1
source/resource/TextsWin.h

@@ -178,7 +178,7 @@
 #define EXTERNAL_EDITOR_HINT    1488
 #define EXTERNAL_EDITOR_HINT    1488
 #define MASK_HINT2              1489
 #define MASK_HINT2              1489
 #define PATH_MASK_HINT2         1490
 #define PATH_MASK_HINT2         1490
-#define CUSTOM_COMMAND_PATTERNS_HINT3 1491
+#define CUSTOM_COMMAND_PATTERNS_HINT4 1491
 #define CLOSED_ON_QUEUE_EMPTY   1492
 #define CLOSED_ON_QUEUE_EMPTY   1492
 #define DIRECTORY_READING_CANCELLED 1493
 #define DIRECTORY_READING_CANCELLED 1493
 #define SYNC_DIR_BROWSE_TOGGLE  1494
 #define SYNC_DIR_BROWSE_TOGGLE  1494

+ 1 - 1
source/resource/TextsWin1.rc

@@ -180,7 +180,7 @@ BEGIN
         EXTERNAL_EDITOR_HINT, "Edit (external)|Edit selected file(s) using external editor '%s'"
         EXTERNAL_EDITOR_HINT, "Edit (external)|Edit selected file(s) using external editor '%s'"
         MASK_HINT2, "* matches any number of characters.\n? matches exactly one character.\n[abc] matches one character from the set.\n[a-z] matches one character from the range.\nExample: *.html; photo??.png"
         MASK_HINT2, "* matches any number of characters.\n? matches exactly one character.\n[abc] matches one character from the set.\n[a-z] matches one character from the range.\nExample: *.html; photo??.png"
         PATH_MASK_HINT2, "Mask can be extended with path mask.\nExample: */public_html/*.html"
         PATH_MASK_HINT2, "Mask can be extended with path mask.\nExample: */public_html/*.html"
-        CUSTOM_COMMAND_PATTERNS_HINT3, "Patterns:\n!! expands to exclamation mark\n! expands to file name\n!& expands to list of selected files (quoted, space-delimited)\n!/ expands to current remote path\n!@ expands to current session hostname\n!U expands to current session username\n!P expands to current session password\n!?prompt[\\]?default! expands to user-entered value with given prompt and default (optional \\ avoids escaping)\n!`command` expands to output of local command\n \nLocal command patterns:\n!^! expands to file name from local panel\n \nExample:\ngrep \"!?Pattern:?!\" !&"
+        CUSTOM_COMMAND_PATTERNS_HINT4, "Patterns:\n!! expands to exclamation mark\n! expands to file name\n!& expands to list of selected files (quoted, space-delimited)\n!/ expands to current remote path\n!S expands to current session URL\n!@ expands to current session hostname\n!U expands to current session username\n!P expands to current session password\n!?prompt[\\]?default! expands to user-entered value with given prompt and default (optional \\ avoids escaping)\n!`command` expands to output of local command\n \nLocal command patterns:\n!^! expands to file name from local panel\n \nExample:\ngrep \"!?Pattern:?!\" !&"
         CLOSED_ON_QUEUE_EMPTY, "All background transfers were finished. Connection was closed."
         CLOSED_ON_QUEUE_EMPTY, "All background transfers were finished. Connection was closed."
         DIRECTORY_READING_CANCELLED, "Reading of remote directory was cancelled."
         DIRECTORY_READING_CANCELLED, "Reading of remote directory was cancelled."
         SYNC_DIR_BROWSE_TOGGLE, "Synchronized browsing was turned %s.|on|off"
         SYNC_DIR_BROWSE_TOGGLE, "Synchronized browsing was turned %s.|on|off"

+ 1 - 0
source/windows/ConsoleRunner.cpp

@@ -2086,6 +2086,7 @@ int __fastcall Console(TConsoleMode Mode)
   try
   try
   {
   {
     UnicodeString ConsoleInstance;
     UnicodeString ConsoleInstance;
+    // First check for /consoleinstance as /console is always used by winscp.com
     if (Params->FindSwitch(L"consoleinstance", ConsoleInstance))
     if (Params->FindSwitch(L"consoleinstance", ConsoleInstance))
     {
     {
       Configuration->Usage->Inc(L"ConsoleExternal");
       Configuration->Usage->Inc(L"ConsoleExternal");

Some files were not shown because too many files changed in this diff