Martin Prikryl 11 years ago
parent
commit
0afcda69fd
43 changed files with 589 additions and 185 deletions
  1. 3 0
      deployment/winscpsetup.iss
  2. 22 7
      dotnet/Session.cs
  3. 1 0
      dotnet/TransferEventArgs.cs
  4. 3 3
      dotnet/properties/AssemblyInfo.cs
  5. 1 1
      readme.txt
  6. 1 1
      source/Console.cbproj
  7. 1 1
      source/DragExt.cbproj
  8. 2 2
      source/DragExt64.rc
  9. 2 1
      source/WinSCP.cbproj
  10. 34 0
      source/core/Common.cpp
  11. 1 0
      source/core/Common.h
  12. 22 2
      source/core/Exceptions.cpp
  13. 2 0
      source/core/Exceptions.h
  14. 11 1
      source/core/FtpFileSystem.cpp
  15. 4 1
      source/core/HierarchicalStorage.cpp
  16. 1 1
      source/core/Interface.h
  17. 3 2
      source/core/ScpFileSystem.cpp
  18. 28 20
      source/core/SecureShell.cpp
  19. 3 4
      source/core/SecureShell.h
  20. 2 1
      source/core/SessionData.cpp
  21. 299 64
      source/core/SftpFileSystem.cpp
  22. 2 0
      source/core/SftpFileSystem.h
  23. 2 2
      source/core/Terminal.cpp
  24. 12 5
      source/filezilla/ApiLog.cpp
  25. 2 0
      source/filezilla/ApiLog.h
  26. 1 0
      source/filezilla/AsyncSocketEx.h
  27. 9 0
      source/filezilla/AsyncSocketExLayer.cpp
  28. 1 0
      source/filezilla/AsyncSocketExLayer.h
  29. 16 5
      source/filezilla/AsyncSslSocketLayer.cpp
  30. 6 0
      source/filezilla/ControlSocket.cpp
  31. 1 0
      source/filezilla/ControlSocket.h
  32. 10 10
      source/forms/CustomScpExplorer.cpp
  33. 2 2
      source/forms/CustomScpExplorer.h
  34. 3 1
      source/forms/MessageDlg.cpp
  35. 1 2
      source/forms/NonVisual.cpp
  36. 7 4
      source/forms/SiteAdvanced.dfm
  37. 7 5
      source/forms/Synchronize.cpp
  38. 5 1
      source/resource/TextsCore.h
  39. 6 2
      source/resource/TextsCore1.rc
  40. 1 1
      source/resource/TextsWin2.rc
  41. 1 0
      source/windows/ConsoleRunner.cpp
  42. 7 2
      source/windows/Setup.cpp
  43. 41 31
      source/windows/WinInterface.cpp

+ 3 - 0
deployment/winscpsetup.iss

@@ -106,6 +106,9 @@ SignTool=sign $f "WinSCP Installer" http://winscp.net/eng/docs/installation
 #endif
 
 [Languages]
+; English has to be first so that it is pre-selected
+; on Setup Select Language window, when no translation matching
+; Windows UI locale is available
 Name: {#DefaultLang}; MessagesFile: {#MessagesPathRel(DefaultLang)}
 
 #define FindHandle

+ 22 - 7
dotnet/Session.cs

@@ -321,6 +321,7 @@ namespace WinSCP
                 using (CreateProgressHandler())
                 {
                     TransferEventArgs args = null;
+                    bool mkdir = false;
 
                     while (groupReader.Read(0))
                     {
@@ -332,22 +333,36 @@ namespace WinSCP
                                 RaiseFileTransferredEvent(args);
                             }
                             args = TransferEventArgs.Read(groupReader);
+                            mkdir = false;
+                        }
+                        else if (groupReader.IsNonEmptyElement(TransferEventArgs.MkDirTag))
+                        {
+                            args = null;
+                            mkdir = true;
+                            // For now, silently ignoring results (even errors)
+                            // of mkdir operation, including future chmod/touch
                         }
                         else if (groupReader.IsNonEmptyElement(ChmodEventArgs.Tag))
                         {
-                            if (args == null)
+                            if (!mkdir)
                             {
-                                throw new InvalidOperationException();
+                                if (args == null)
+                                {
+                                    throw new InvalidOperationException();
+                                }
+                                args.Chmod = ChmodEventArgs.Read(groupReader);
                             }
-                            args.Chmod = ChmodEventArgs.Read(groupReader);
                         }
                         else if (groupReader.IsNonEmptyElement(TouchEventArgs.Tag))
                         {
-                            if (args == null)
+                            if (!mkdir)
                             {
-                                throw new InvalidOperationException();
+                                if (args == null)
+                                {
+                                    throw new InvalidOperationException();
+                                }
+                                args.Touch = TouchEventArgs.Read(groupReader);
                             }
-                            args.Touch = TouchEventArgs.Read(groupReader);
                         }
                     }
 
@@ -649,7 +664,7 @@ namespace WinSCP
                 WriteCommand(string.Format(CultureInfo.InvariantCulture, "mkdir \"{0}\"", ArgumentEscape(path)));
 
                 using (ElementLogReader groupReader = _reader.WaitForGroupAndCreateLogReader())
-                using (ElementLogReader mkdirReader = groupReader.WaitForNonEmptyElementAndCreateLogReader("mkdir", LogReadFlags.ThrowFailures))
+                using (ElementLogReader mkdirReader = groupReader.WaitForNonEmptyElementAndCreateLogReader(TransferEventArgs.MkDirTag, LogReadFlags.ThrowFailures))
                 {
                     ReadElement(mkdirReader, 0);
                     groupReader.ReadToEnd(LogReadFlags.ThrowFailures);

+ 1 - 0
dotnet/TransferEventArgs.cs

@@ -15,6 +15,7 @@ namespace WinSCP
 
         internal const string UploadTag = "upload";
         internal const string DownloadTag = "download";
+        internal const string MkDirTag = "mkdir";
 
         internal TransferEventArgs()
         {

+ 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
 [assembly: Guid("a0b93468-d98a-4845-a234-8076229ad93f")]
 
-[assembly: AssemblyVersion("1.2.0.0")]
-[assembly: AssemblyFileVersion("1.2.0.0")]
-[assembly: AssemblyInformationalVersionAttribute("5.6.0.0")]
+[assembly: AssemblyVersion("1.2.1.0")]
+[assembly: AssemblyFileVersion("1.2.1.0")]
+[assembly: AssemblyInformationalVersionAttribute("5.6.1.0")]
 
 [assembly: CLSCompliant(true)]
 

+ 1 - 1
readme.txt

@@ -1,7 +1,7 @@
 This is the README file for source code package of WinSCP.
 
 To build WinSCP you need:
-- Embarcadero C++ Builder XE2 Professional.
+- Embarcadero C++ Builder XE6 Professional.
   http://www.embarcadero.com/products/cbuilder
 - Microsoft .NET Framework 3.5 (for MSBuild)
   http://www.microsoft.com/netframework

+ 1 - 1
source/Console.cbproj

@@ -65,7 +65,7 @@
 			<ProjectType>CppConsoleApplication</ProjectType>
 			<SanitizedProjectName>Console</SanitizedProjectName>
 			<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.0.0;ReleaseType=stable;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.1.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MajorVer>4</VerInfo_MajorVer>
 			<VerInfo_MinorVer>1</VerInfo_MinorVer>

+ 1 - 1
source/DragExt.cbproj

@@ -66,7 +66,7 @@
 			<SanitizedProjectName>DragExt</SanitizedProjectName>
 			<VerInfo_DLL>true</VerInfo_DLL>
 			<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.0.0;ReleaseType=stable;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.1.0;ReleaseType=stable;WWW=http://winscp.net/</VerInfo_Keys>
 			<VerInfo_Locale>1033</VerInfo_Locale>
 			<VerInfo_MinorVer>2</VerInfo_MinorVer>
 			<VerInfo_Release>1</VerInfo_Release>

+ 2 - 2
source/DragExt64.rc

@@ -1,6 +1,6 @@
 1 VERSIONINFO
 FILEVERSION 1,2,1,0
-PRODUCTVERSION 5,6,0,0
+PRODUCTVERSION 5,6,1,0
 FILEOS 0x4
 FILETYPE 0x2
 {
@@ -16,7 +16,7 @@ FILETYPE 0x2
             VALUE "LegalTrademarks", "\0"
             VALUE "OriginalFilename", "dragext64.dll\0"
             VALUE "ProductName", "WinSCP\0"
-            VALUE "ProductVersion", "5.6.0.0\0"
+            VALUE "ProductVersion", "5.6.1.0\0"
             VALUE "ReleaseType", "stable\0"
             VALUE "WWW", "http://winscp.net/\0"
         }

+ 2 - 1
source/WinSCP.cbproj

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

+ 34 - 0
source/core/Common.cpp

@@ -335,6 +335,22 @@ bool ExtractMainInstructions(UnicodeString & S, UnicodeString & MainInstructions
   return Result;
 }
 //---------------------------------------------------------------------------
+static int FindInteractiveMsgStart(const UnicodeString & S)
+{
+  int Result = 0;
+  UnicodeString InteractiveMsgTag = LoadStr(INTERACTIVE_MSG_TAG);
+  if (EndsStr(InteractiveMsgTag, S) &&
+      (S.Length() >= 2 * InteractiveMsgTag.Length()))
+  {
+    Result = S.Length() - 2 * InteractiveMsgTag.Length() + 1;
+    while ((Result > 0) && (S.SubString(Result, InteractiveMsgTag.Length()) != InteractiveMsgTag))
+    {
+      Result--;
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 UnicodeString UnformatMessage(UnicodeString S)
 {
   UnicodeString MainInstruction;
@@ -342,6 +358,24 @@ UnicodeString UnformatMessage(UnicodeString S)
   {
     S = MainInstruction + S;
   }
+
+  int InteractiveMsgStart = FindInteractiveMsgStart(S);
+  if (InteractiveMsgStart > 0)
+  {
+    S = S.SubString(1, InteractiveMsgStart - 1);
+  }
+  return S;
+}
+//---------------------------------------------------------------------------
+UnicodeString RemoveInteractiveMsgTag(UnicodeString S)
+{
+  int InteractiveMsgStart = FindInteractiveMsgStart(S);
+  if (InteractiveMsgStart > 0)
+  {
+    UnicodeString InteractiveMsgTag = LoadStr(INTERACTIVE_MSG_TAG);
+    S.Delete(InteractiveMsgStart, InteractiveMsgTag.Length());
+    S.Delete(S.Length() - InteractiveMsgTag.Length() + 1, InteractiveMsgTag.Length());
+  }
   return S;
 }
 //---------------------------------------------------------------------------

+ 1 - 0
source/core/Common.h

@@ -50,6 +50,7 @@ UnicodeString __fastcall MainInstructions(const UnicodeString & S);
 UnicodeString __fastcall MainInstructionsFirstParagraph(const UnicodeString & S);
 bool ExtractMainInstructions(UnicodeString & S, UnicodeString & MainInstructions);
 UnicodeString UnformatMessage(UnicodeString S);
+UnicodeString RemoveInteractiveMsgTag(UnicodeString S);
 bool IsNumber(const UnicodeString Str);
 UnicodeString __fastcall SystemTemporaryDirectory();
 UnicodeString __fastcall GetShellFolderPath(int CSIdl);

+ 22 - 2
source/core/Exceptions.cpp

@@ -8,6 +8,7 @@
 #include "HelpCore.h"
 #include "Configuration.h"
 #include "CoreMain.h"
+#include "Interface.h"
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 //---------------------------------------------------------------------------
@@ -311,6 +312,11 @@ void __fastcall ExtException::AddMoreMessages(Exception* E)
       FMoreMessages->Insert(0, UnformatMessage(Msg));
     }
 
+    if (IsInternalException(E))
+    {
+      AppendExceptionStackTraceAndForget(FMoreMessages);
+    }
+
     if (FMoreMessages->Count == 0)
     {
       delete FMoreMessages;
@@ -324,9 +330,14 @@ __fastcall ExtException::~ExtException()
   delete FMoreMessages;
 }
 //---------------------------------------------------------------------------
+ExtException * __fastcall ExtException::CloneFrom(Exception * E)
+{
+  return new ExtException(E, L"");
+}
+//---------------------------------------------------------------------------
 ExtException * __fastcall ExtException::Clone()
 {
-  return new ExtException(this, L"");
+  return CloneFrom(this);
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall SysErrorMessageForError(int LastError)
@@ -402,7 +413,16 @@ Exception * __fastcall CloneException(Exception * E)
   }
   else
   {
-    Result = new Exception(E->Message);
+    // we do not expect this to happen
+    if (ALWAYS_FALSE(IsInternalException(E)))
+    {
+      // to save exception stack trace
+      Result = ExtException::CloneFrom(E);
+    }
+    else
+    {
+      Result = new Exception(E->Message);
+    }
   }
   return Result;
 }

+ 2 - 0
source/core/Exceptions.h

@@ -54,6 +54,8 @@ public:
   {
   }
 
+  static ExtException * __fastcall CloneFrom(Exception * E);
+
   virtual ExtException * __fastcall Clone();
 
 protected:

+ 11 - 1
source/core/FtpFileSystem.cpp

@@ -1880,10 +1880,20 @@ void __fastcall TFTPFileSystem::ReadCurrentDirectory()
 
           if (Unquote(Path))
           {
-            FCurrentDirectory = UnixExcludeTrailingBackslash(Path);
             Result = true;
           }
         }
+        else
+        {
+          P = Path.Pos(L" ");
+          Path.Delete(P, Path.Length() - P + 1);
+          Result = true;
+        }
+
+        if (Result)
+        {
+          FCurrentDirectory = UnixExcludeTrailingBackslash(Path);
+        }
       }
 
       if (Result)

+ 4 - 1
source/core/HierarchicalStorage.cpp

@@ -102,7 +102,10 @@ __fastcall THierarchicalStorage::THierarchicalStorage(const UnicodeString AStora
   FKeyHistory = new TStringList();
   AccessMode = smRead;
   Explicit = false;
-  ForceAnsi = true;
+  // While this was implemented in 5.0 already, for some reason
+  // it was disabled (by mistake?). So although enabled for 5.6.1 only,
+  // data written in Unicode/UTF8 can be read by all versions back to 5.0.
+  ForceAnsi = false;
   MungeStringValues = true;
 }
 //---------------------------------------------------------------------------

+ 1 - 1
source/core/Interface.h

@@ -9,7 +9,7 @@
 TConfiguration * __fastcall CreateConfiguration();
 
 void __fastcall ShowExtendedException(Exception * E);
-bool __fastcall AppendExceptionStackTrace(TStrings *& MoreMessages);
+bool __fastcall AppendExceptionStackTraceAndForget(TStrings *& MoreMessages);
 
 UnicodeString __fastcall GetCompanyRegistryKey();
 UnicodeString __fastcall GetRegistryKey();

+ 3 - 2
source/core/ScpFileSystem.cpp

@@ -298,8 +298,6 @@ __fastcall TSCPFileSystem::TSCPFileSystem(TTerminal * ATerminal, TSecureShell *
 
   FFileSystemInfo.ProtocolBaseName = L"SCP";
   FFileSystemInfo.ProtocolName = FFileSystemInfo.ProtocolBaseName;
-  // capabilities of SCP protocol are fixed
-  FTerminal->SaveCapabilities(FFileSystemInfo);
 }
 //---------------------------------------------------------------------------
 __fastcall TSCPFileSystem::~TSCPFileSystem()
@@ -686,6 +684,9 @@ UnicodeString __fastcall TSCPFileSystem::GetCurrentDirectory()
 //---------------------------------------------------------------------------
 void __fastcall TSCPFileSystem::DoStartup()
 {
+  // Capabilities of SCP protocol are fixed
+  FTerminal->SaveCapabilities(FFileSystemInfo);
+
   // SkipStartupMessage and DetectReturnVar must succeed,
   // otherwise session is to be closed.
   try

+ 28 - 20
source/core/SecureShell.cpp

@@ -50,8 +50,7 @@ __fastcall TSecureShell::TSecureShell(TSessionUI* UI,
   FActive = false;
   FWaiting = 0;
   FOpened = false;
-  FOpenSSH = false;
-  FProFTPD = false;
+  FSshImplementation = sshiUnknown;
   OutPtr = NULL;
   Pending = NULL;
   FBackendHandle = NULL;
@@ -135,16 +134,6 @@ const TSessionInfo & __fastcall TSecureShell::GetSessionInfo()
   }
   return FSessionInfo;
 }
-//---------------------------------------------------------------------------
-bool __fastcall TSecureShell::IsOpenSSH()
-{
-  return FOpenSSH;
-}
-//---------------------------------------------------------------------------
-bool __fastcall TSecureShell::IsProFTPD()
-{
-  return FProFTPD;
-}
 //---------------------------------------------------------------------
 Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
 {
@@ -425,12 +414,27 @@ void __fastcall TSecureShell::Open()
   FOpened = true;
 
   UnicodeString SshImplementation = GetSessionInfo().SshImplementation;
-  FOpenSSH =
-    // Sun SSH is based on OpenSSH (suffers the same bugs)
-    (SshImplementation.Pos(L"OpenSSH") == 1) ||
-    (SshImplementation.Pos(L"Sun_SSH") == 1);
-  FProFTPD =
-    (SshImplementation.Pos(L"mod_sftp") == 1);
+  if (// e.g. "OpenSSH_5.3"
+      (SshImplementation.Pos(L"OpenSSH") == 1) ||
+      // Sun SSH is based on OpenSSH (suffers the same bugs)
+      (SshImplementation.Pos(L"Sun_SSH") == 1))
+  {
+    FSshImplementation = sshiOpenSSH;
+  }
+  // e.g. "mod_sftp/0.9.8"
+  else if (SshImplementation.Pos(L"mod_sftp") == 1)
+  {
+    FSshImplementation = sshiProFTPD;
+  }
+  // e.g. "5.25 FlowSsh: Bitvise SSH Server (WinSSHD) 6.07: free only for personal non-commercial use"
+  else if (SshImplementation.Pos(L"FlowSsh") > 0)
+  {
+    FSshImplementation = sshiBitvise;
+  }
+  else
+  {
+    FSshImplementation = sshiUnknown;
+  }
 }
 //---------------------------------------------------------------------------
 bool __fastcall TSecureShell::TryFtp()
@@ -2225,14 +2229,18 @@ void __fastcall TSecureShell::CollectUsage()
     Configuration->Usage->Inc(L"OpenedSessionsSSH2");
   }
 
-  if (FOpenSSH)
+  if (SshImplementation == sshiOpenSSH)
   {
     Configuration->Usage->Inc(L"OpenedSessionsSSHOpenSSH");
   }
-  else if (FProFTPD)
+  else if (SshImplementation == sshiProFTPD)
   {
     Configuration->Usage->Inc(L"OpenedSessionsSSHProFTPD");
   }
+  else if (SshImplementation == sshiBitvise)
+  {
+    Configuration->Usage->Inc(L"OpenedSessionsSSHBitvise");
+  }
   else
   {
     Configuration->Usage->Inc(L"OpenedSessionsSSHOther");

+ 3 - 4
source/core/SecureShell.h

@@ -17,6 +17,7 @@ typedef struct _WSANETWORKEVENTS WSANETWORKEVENTS;
 typedef UINT_PTR SOCKET;
 typedef std::set<SOCKET> TSockets;
 struct TPuttyTranslation;
+enum TSshImplementation { sshiUnknown, sshiOpenSSH, sshiProFTPD, sshiBitvise };
 //---------------------------------------------------------------------------
 class TSecureShell
 {
@@ -48,8 +49,7 @@ private:
   bool FNoConnectionResponse;
   bool FCollectPrivateKeyUsage;
   int FWaitingForData;
-  bool FOpenSSH;
-  bool FProFTPD;
+  TSshImplementation FSshImplementation;
 
   unsigned PendLen;
   unsigned PendSize;
@@ -133,8 +133,6 @@ public:
   void __fastcall ClearStdError();
   bool __fastcall GetStoredCredentialsTried();
   void __fastcall CollectUsage();
-  bool __fastcall IsOpenSSH();
-  bool __fastcall IsProFTPD();
 
   void __fastcall RegisterReceiveHandler(TNotifyEvent Handler);
   void __fastcall UnregisterReceiveHandler(TNotifyEvent Handler);
@@ -164,6 +162,7 @@ public:
   __property UnicodeString LastTunnelError = { read = FLastTunnelError };
   __property UnicodeString UserName = { read = FUserName };
   __property bool Simple = { read = FSimple, write = FSimple };
+  __property TSshImplementation SshImplementation = { read = FSshImplementation };
 };
 //---------------------------------------------------------------------------
 #endif

+ 2 - 1
source/core/SessionData.cpp

@@ -11,6 +11,7 @@
 #include "TextsCore.h"
 #include "PuttyIntf.h"
 #include "RemoteFiles.h"
+#include "SFTPFileSystem.h"
 #include <StrUtils.hpp>
 #include <XMLDoc.hpp>
 #include <StrUtils.hpp>
@@ -168,7 +169,7 @@ void __fastcall TSessionData::Default()
   SFTPDownloadQueue = 4;
   SFTPUploadQueue = 4;
   SFTPListingQueue = 2;
-  SFTPMaxVersion = 5;
+  SFTPMaxVersion = ::SFTPMaxVersion;
   SFTPMaxPacketSize = 0;
 
   for (unsigned int Index = 0; Index < LENOF(FSFTPBugs); Index++)

+ 299 - 64
source/core/SftpFileSystem.cpp

@@ -47,6 +47,7 @@
 #define SSH_FXP_RENAME             18
 #define SSH_FXP_READLINK           19
 #define SSH_FXP_SYMLINK            20
+#define SSH_FXP_LINK               21
 #define SSH_FXP_STATUS             101
 #define SSH_FXP_HANDLE             102
 #define SSH_FXP_DATA               103
@@ -68,6 +69,12 @@
 #define SSH_FILEXFER_ATTR_OWNERGROUP        0x00000080
 #define SSH_FILEXFER_ATTR_SUBSECOND_TIMES   0x00000100
 #define SSH_FILEXFER_ATTR_BITS              0x00000200
+#define SSH_FILEXFER_ATTR_ALLOCATION_SIZE   0x00000400
+#define SSH_FILEXFER_ATTR_TEXT_HINT         0x00000800
+#define SSH_FILEXFER_ATTR_MIME_TYPE         0x00001000
+#define SSH_FILEXFER_ATTR_LINK_COUNT        0x00002000
+#define SSH_FILEXFER_ATTR_UNTRANSLATED_NAME 0x00004000
+#define SSH_FILEXFER_ATTR_CTIME             0x00008000
 #define SSH_FILEXFER_ATTR_EXTENDED          0x80000000
 
 #define SSH_FILEXFER_ATTR_COMMON \
@@ -98,9 +105,6 @@
 #define SSH_FXF_ACCESS_APPEND_DATA        0x00000008
 #define SSH_FXF_ACCESS_APPEND_DATA_ATOMIC 0x00000010
 #define SSH_FXF_ACCESS_TEXT_MODE          0x00000020
-#define SSH_FXF_ACCESS_READ_LOCK          0x00000040
-#define SSH_FXF_ACCESS_WRITE_LOCK         0x00000080
-#define SSH_FXF_ACCESS_DELETE_LOCK        0x00000100
 
 #define ACE4_READ_DATA         0x00000001
 #define ACE4_LIST_DIRECTORY    0x00000001
@@ -139,13 +143,15 @@
 #define SFTP_EXT_STATVFS_VALUE_V2 L"2"
 #define SFTP_EXT_STATVFS_ST_RDONLY 0x1
 #define SFTP_EXT_STATVFS_ST_NOSUID 0x2
+#define SFTP_EXT_HARDLINK "[email protected]"
+#define SFTP_EXT_HARDLINK_VALUE_V1 L"1"
 #define SFTP_EXT_COPY_FILE "copy-file"
 //---------------------------------------------------------------------------
 #define OGQ_LIST_OWNERS 0x01
 #define OGQ_LIST_GROUPS 0x02
 //---------------------------------------------------------------------------
 const int SFTPMinVersion = 0;
-const int SFTPMaxVersion = 5;
+const int SFTPMaxVersion = 6;
 const unsigned int SFTPNoMessageNumber = static_cast<unsigned int>(-1);
 
 const int asNo =            0;
@@ -197,8 +203,8 @@ struct TSFTPSupport
     OpenFlags = 0;
     AccessMask = 0;
     MaxReadSize = 0;
-    OpenBlockMasks = 0;
-    BlockMasks = 0;
+    OpenBlockVector = 0;
+    BlockVector = 0;
     AttribExtensions->Clear();
     Extensions->Clear();
     Loaded = false;
@@ -209,8 +215,8 @@ struct TSFTPSupport
   unsigned int OpenFlags;
   unsigned int AccessMask;
   unsigned int MaxReadSize;
-  unsigned int OpenBlockMasks;
-  unsigned int BlockMasks;
+  unsigned int OpenBlockVector;
+  unsigned int BlockVector;
   TStrings * AttribExtensions;
   TStrings * Extensions;
   bool Loaded;
@@ -293,6 +299,11 @@ public:
     Add(&Value, sizeof(Value));
   }
 
+  void AddBool(bool Value)
+  {
+    AddByte(Value ? 1 : 0);
+  }
+
   void AddCardinal(unsigned long Value)
   {
     // duplicated in Reuse()
@@ -347,6 +358,11 @@ public:
     AddString(Value, Utf);
   }
 
+  unsigned int AllocationSizeAttribute(int Version)
+  {
+    return (Version >= 6) ? SSH_FILEXFER_ATTR_ALLOCATION_SIZE : SSH_FILEXFER_ATTR_SIZE;
+  }
+
   void AddProperties(unsigned short * Rights, TRemoteToken * Owner,
     TRemoteToken * Group, __int64 * MTime, __int64 * ATime,
     __int64 * Size, bool IsDirectory, int Version, bool Utf)
@@ -354,7 +370,7 @@ public:
     int Flags = 0;
     if (Size != NULL)
     {
-      Flags |= SSH_FILEXFER_ATTR_SIZE;
+      Flags |= AllocationSizeAttribute(Version);
     }
     // both or neither
     assert((Owner != NULL) == (Group != NULL));
@@ -397,6 +413,8 @@ public:
 
     if (Size != NULL)
     {
+      // this is SSH_FILEXFER_ATTR_SIZE for version <= 5, but
+      // SSH_FILEXFER_ATTR_ALLOCATION_SIZE for version >= 6
       AddInt64(*Size);
     }
 
@@ -509,28 +527,46 @@ public:
   {
     Need(sizeof(unsigned char));
     unsigned char Result = FData[FPosition];
-    FPosition++;
+    DataConsumed(sizeof(unsigned char));
     return Result;
   }
 
+  bool GetBool()
+  {
+    return (GetByte() != 0);
+  }
+
+  bool CanGetBool()
+  {
+    return (RemainingLength >= sizeof(unsigned char));
+  }
+
   unsigned long GetCardinal()
   {
-    unsigned long Result;
-    Need(sizeof(Result));
-    Result = GET_32BIT(FData + FPosition);
-    FPosition += sizeof(Result);
+    unsigned long Result = PeekCardinal();
+    DataConsumed(sizeof(Result));
     return Result;
   }
 
+  bool CanGetCardinal()
+  {
+    return (RemainingLength >= sizeof(unsigned long));
+  }
+
   unsigned long GetSmallCardinal()
   {
     unsigned long Result;
     Need(2);
     Result = (FData[FPosition] << 8) + FData[FPosition + 1];
-    FPosition += 2;
+    DataConsumed(2);
     return Result;
   }
 
+  bool CanGetSmallCardinal()
+  {
+    return (RemainingLength >= 2);
+  }
+
   __int64 GetInt64()
   {
     __int64 Hi = GetCardinal();
@@ -547,7 +583,19 @@ public:
     assert(Len < SFTP_MAX_PACKET_LEN);
     Result.SetLength(Len);
     memcpy(Result.c_str(), FData + FPosition, Len);
-    FPosition += Len;
+    DataConsumed(Len);
+    return Result;
+  }
+
+  bool CanGetString(unsigned int & Size)
+  {
+    bool Result = CanGetCardinal();
+    if (Result)
+    {
+      unsigned long Len = PeekCardinal();
+      Size = (sizeof(Len) + Len);
+      Result = (Size <= RemainingLength);
+    }
     return Result;
   }
 
@@ -623,6 +671,11 @@ public:
     {
       File->Size = GetInt64();
     }
+    // SFTP-6 only
+    if (Flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE)
+    {
+      GetInt64(); // skip
+    }
     // SSH-2.0-3.2.0 F-SECURE SSH - Process Software MultiNet
     // sets SSH_FILEXFER_ATTR_UIDGID for v4, but does not include the UID/GUID
     if ((Flags & SSH_FILEXFER_ATTR_UIDGID) && (Version < 4))
@@ -690,6 +743,15 @@ public:
       {
         File->Modification = Now();
       }
+      // SFTP-6
+      if (Flags & SSH_FILEXFER_ATTR_CTIME)
+      {
+        GetInt64(); // skip attribute modification time
+        if (Flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES)
+        {
+          GetCardinal(); // skip attribute modification time subseconds
+        }
+      }
     }
 
     if (Flags & SSH_FILEXFER_ATTR_ACL)
@@ -702,12 +764,35 @@ public:
       // while SSH_FILEXFER_ATTR_BITS is defined for SFTP5 only, vandyke 2.3.3 sets it
       // for SFTP4 as well
       unsigned long Bits = GetCardinal();
+      if (Version >= 6)
+      {
+        unsigned long BitsValid = GetCardinal();
+        Bits = Bits & BitsValid;
+      }
       if (FLAGSET(Bits, SSH_FILEXFER_ATTR_FLAGS_HIDDEN))
       {
         File->IsHidden = true;
       }
     }
 
+    // skip some SFTP-6 only fields
+    if (Flags & SSH_FILEXFER_ATTR_TEXT_HINT)
+    {
+      GetByte();
+    }
+    if (Flags & SSH_FILEXFER_ATTR_MIME_TYPE)
+    {
+      GetAnsiString();
+    }
+    if (Flags & SSH_FILEXFER_ATTR_LINK_COUNT)
+    {
+      GetCardinal();
+    }
+    if (Flags & SSH_FILEXFER_ATTR_UNTRANSLATED_NAME)
+    {
+      GetPathString(Utf);
+    }
+
     if ((Version < 4) && (Type != SSH_FXP_ATTRS))
     {
       try
@@ -767,6 +852,11 @@ public:
     return FPosition < FLength ? FData + FPosition : NULL;
   }
 
+  void DataConsumed(unsigned int Size)
+  {
+    FPosition += Size;
+  }
+
   void DataUpdated(int ALength)
   {
     FPosition = 0;
@@ -963,6 +1053,7 @@ private:
       TYPE_CASE(SSH_FXP_RENAME);
       TYPE_CASE(SSH_FXP_READLINK);
       TYPE_CASE(SSH_FXP_SYMLINK);
+      TYPE_CASE(SSH_FXP_LINK);
       TYPE_CASE(SSH_FXP_STATUS);
       TYPE_CASE(SSH_FXP_HANDLE);
       TYPE_CASE(SSH_FXP_DATA);
@@ -995,11 +1086,19 @@ private:
 
   inline void Need(unsigned int Size)
   {
-    if (FPosition + Size > FLength)
+    if (Size > RemainingLength)
     {
       throw Exception(FMTLOAD(SFTP_PACKET_ERROR, (int(FPosition), int(Size), int(FLength))));
     }
   }
+
+  unsigned long PeekCardinal()
+  {
+    unsigned long Result;
+    Need(sizeof(Result));
+    Result = GET_32BIT(FData + FPosition);
+    return Result;
+  }
 };
 //---------------------------------------------------------------------------
 int TSFTPPacket::FMessageCounter = 0;
@@ -1762,6 +1861,11 @@ void __fastcall TSFTPFileSystem::CollectUsage()
     case 5:
       VersionCounter = L"OpenedSessionsSFTP5";
       break;
+    case 6:
+      VersionCounter = L"OpenedSessionsSFTP6";
+      break;
+    default:
+      FAIL;
   }
   FTerminal->Configuration->Usage->Inc(VersionCounter);
 }
@@ -1874,7 +1978,6 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
   switch (Capability) {
     case fcAnyCommand:
     case fcShellAnyCommand:
-    case fcHardLink:
       return false;
 
     case fcNewerOnlyUpload:
@@ -1931,18 +2034,35 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
 
     case fcCheckingSpaceAvailable:
       return
-        // extension announced in estension list of by
+        // extension announced in extension list of by
         // SFTP_EXT_SUPPORTED/SFTP_EXT_SUPPORTED2 extension
         // (SFTP version 5 and newer only)
         SupportsExtension(SFTP_EXT_SPACE_AVAILABLE) ||
         // extension announced by proprietary SFTP_EXT_STATVFS extension
-        FSupportsStatVfsV2;
+        FSupportsStatVfsV2 ||
+        // Bitwise (as of 6.07) fails to report it's supported extensions.
+        (FSecureShell->SshImplementation == sshiBitvise);
 
     case fcCalculatingChecksum:
-      return SupportsExtension(SFTP_EXT_CHECK_FILE);
+      return
+        // Specification says that "check-file" should be announced,
+        // yet Vandyke VShell (as of 4.0.3) announce "check-file-name"
+        // https://forums.vandyke.com/showthread.php?t=11597
+        SupportsExtension(SFTP_EXT_CHECK_FILE) ||
+        SupportsExtension(SFTP_EXT_CHECK_FILE_NAME) ||
+        // see above
+        (FSecureShell->SshImplementation == sshiBitvise);
 
     case fcRemoteCopy:
-      return SupportsExtension(SFTP_EXT_COPY_FILE);
+      return
+        SupportsExtension(SFTP_EXT_COPY_FILE) ||
+        // see above
+        (FSecureShell->SshImplementation == sshiBitvise);
+
+    case fcHardLink:
+      return
+        (FVersion >= 6) ||
+        FSupportsHardlink;
 
     default:
       FAIL;
@@ -2128,7 +2248,10 @@ unsigned long __fastcall TSFTPFileSystem::GotStatusPacket(TSFTPPacket * Packet,
     SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT,
     SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED,
     SFTP_STATUS_DELETE_PENDING,
-    SFTP_STATUS_FILE_CORRUPT
+    SFTP_STATUS_FILE_CORRUPT,
+    SFTP_STATUS_OWNER_INVALID,
+    SFTP_STATUS_GROUP_INVALID,
+    SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK
   };
   int Message;
   if ((AllowStatus & (0x01 << Code)) == 0)
@@ -2674,6 +2797,7 @@ void __fastcall TSFTPFileSystem::DoStartup()
   FEOL = "\r\n";
   FSupport->Loaded = false;
   FSupportsStatVfsV2 = false;
+  FSupportsHardlink = false;
   SAFE_DESTROY(FFixedPaths);
 
   if (FVersion >= 3)
@@ -2715,8 +2839,11 @@ void __fastcall TSFTPFileSystem::DoStartup()
         }
         else
         {
-          FSupport->OpenBlockMasks = SupportedStruct.GetSmallCardinal();
-          FSupport->BlockMasks = SupportedStruct.GetSmallCardinal();
+          // note that supported-open-block-vector, supported-block-vector,
+          // attrib-extension-count and attrib-extension-names fields
+          // were added only in rev 08, while "supported2" was defined in rev 07
+          FSupport->OpenBlockVector = SupportedStruct.GetSmallCardinal();
+          FSupport->BlockVector = SupportedStruct.GetSmallCardinal();
           unsigned int ExtensionCount;
           ExtensionCount = SupportedStruct.GetCardinal();
           for (unsigned int i = 0; i < ExtensionCount; i++)
@@ -2733,15 +2860,16 @@ void __fastcall TSFTPFileSystem::DoStartup()
         if (FTerminal->Log->Logging)
         {
           FTerminal->LogEvent(FORMAT(
-            L"Server support information:\n"
+            L"Server support information (%s):\n"
              "  Attribute mask: %x, Attribute bits: %x, Open flags: %x\n"
-             "  Access mask: %x, Open block masks: %x, Block masks: %x, Max read size: %d\n",
-            (int(FSupport->AttributeMask),
+             "  Access mask: %x, Open block vector: %x, Block vector: %x, Max read size: %d\n",
+            (ExtensionName,
+             int(FSupport->AttributeMask),
              int(FSupport->AttributeBits),
              int(FSupport->OpenFlags),
              int(FSupport->AccessMask),
-             int(FSupport->OpenBlockMasks),
-             int(FSupport->BlockMasks),
+             int(FSupport->OpenBlockVector),
+             int(FSupport->BlockVector),
              int(FSupport->MaxReadSize))));
           FTerminal->LogEvent(FORMAT(L"  Attribute extensions (%d)\n", (FSupport->AttribExtensions->Count)));
           for (int Index = 0; Index < FSupport->AttribExtensions->Count; Index++)
@@ -2801,29 +2929,27 @@ void __fastcall TSFTPFileSystem::DoStartup()
       }
       else if (ExtensionName == SFTP_EXT_VERSIONS)
       {
-        try
+        // first try legacy decoding according to incorrect encoding
+        // (structure-like) as of VShell (bug no longer present as of 4.0.3).
+        TSFTPPacket VersionsPacket(ExtensionData);
+        unsigned int StringSize;
+        if (VersionsPacket.CanGetString(StringSize) &&
+            (StringSize == VersionsPacket.RemainingLength))
         {
-          // first try legacy decoding according to incorrect encoding
-          // (structure-like) as of VShell.
-          TSFTPPacket VersionsPacket(ExtensionData);
           UnicodeString Versions = VersionsPacket.GetAnsiString();
-          if (VersionsPacket.GetNextData() != NULL)
-          {
-            Abort();
-          }
           FTerminal->LogEvent(FORMAT(L"SFTP versions supported by the server (VShell format): %s",
             (Versions)));
         }
-        catch(...)
+        else
         {
           // if that fails, fallback to proper decoding
           FTerminal->LogEvent(FORMAT(L"SFTP versions supported by the server: %s",
-            (AnsiString(ExtensionData.c_str()))));
+            (UnicodeString(AnsiString(ExtensionData.c_str())))));
         }
       }
       else if (ExtensionName == SFTP_EXT_STATVFS)
       {
-        UnicodeString StatVfsVersion = AnsiString(ExtensionData.c_str());
+        UnicodeString StatVfsVersion = UnicodeString(AnsiString(ExtensionData.c_str()));
         if (StatVfsVersion == SFTP_EXT_STATVFS_VALUE_V2)
         {
           FSupportsStatVfsV2 = true;
@@ -2834,6 +2960,19 @@ void __fastcall TSFTPFileSystem::DoStartup()
           FTerminal->LogEvent(FORMAT(L"Unsupported %s extension version %s", (ExtensionName, ExtensionDisplayData)));
         }
       }
+      else if (ExtensionName == SFTP_EXT_HARDLINK)
+      {
+        UnicodeString HardlinkVersion = UnicodeString(AnsiString(ExtensionData.c_str()));
+        if (HardlinkVersion == SFTP_EXT_HARDLINK_VALUE_V1)
+        {
+          FSupportsHardlink = true;
+          FTerminal->LogEvent(FORMAT(L"Supports %s extension version %s", (ExtensionName, ExtensionDisplayData)));
+        }
+        else
+        {
+          FTerminal->LogEvent(FORMAT(L"Unsupported %s extension version %s", (ExtensionName, ExtensionDisplayData)));
+        }
+      }
       else
       {
         FTerminal->LogEvent(FORMAT(L"Unknown server extension %s=%s",
@@ -2852,7 +2991,8 @@ void __fastcall TSFTPFileSystem::DoStartup()
       Packet.AddInt64(LOWORD(FTerminal->Configuration->FixedApplicationInfo->dwFileVersionLS));
       SendPacket(&Packet);
       // we are not interested in the response, do not wait for it
-      ReserveResponse(&Packet, NULL);
+      ReceiveResponse(&Packet, &Packet);
+      //ReserveResponse(&Packet, NULL);
     }
   }
 
@@ -2890,7 +3030,7 @@ void __fastcall TSFTPFileSystem::DoStartup()
   FMaxPacketSize = FTerminal->SessionData->SFTPMaxPacketSize;
   if (FMaxPacketSize == 0)
   {
-    if (FSecureShell->IsOpenSSH() && (FVersion == 3) && !FSupport->Loaded)
+    if ((FSecureShell->SshImplementation == sshiOpenSSH) && (FVersion == 3) && !FSupport->Loaded)
     {
       FMaxPacketSize = 4 + (256 * 1024); // len + 256kB payload
       FTerminal->LogEvent(FORMAT(L"Limiting packet size to OpenSSH sftp-server limit of %d bytes",
@@ -3122,6 +3262,11 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
           }
         }
 
+        if ((FVersion >= 6) && ListingPacket.CanGetBool())
+        {
+          isEOF = ListingPacket.GetBool();
+        }
+
         if (Count == 0)
         {
           FTerminal->LogEvent(L"Empty directory listing packet. Aborting directory reading.");
@@ -3386,13 +3531,13 @@ void __fastcall TSFTPFileSystem::RenameFile(const UnicodeString FileName,
 void __fastcall TSFTPFileSystem::CopyFile(const UnicodeString FileName,
   const UnicodeString NewName)
 {
-  // Implemented by ProFTPD/mod_sftp
-  assert(SupportsExtension(SFTP_EXT_COPY_FILE));
+  // Implemented by ProFTPD/mod_sftp and Bitvise WinSSHD (without announcing it)
+  assert(SupportsExtension(SFTP_EXT_COPY_FILE) || (FSecureShell->SshImplementation == sshiBitvise));
   TSFTPPacket Packet(SSH_FXP_EXTENDED);
   Packet.AddString(SFTP_EXT_COPY_FILE);
   Packet.AddPathString(Canonify(FileName), FUtfStrings);
   Packet.AddPathString(Canonify(NewName), FUtfStrings);
-  Packet.AddByte(0);
+  Packet.AddBool(false);
   SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS);
 }
 //---------------------------------------------------------------------------
@@ -3408,26 +3553,95 @@ void __fastcall TSFTPFileSystem::CreateDirectory(const UnicodeString DirName)
 void __fastcall TSFTPFileSystem::CreateLink(const UnicodeString FileName,
   const UnicodeString PointTo, bool Symbolic)
 {
-  USEDPARAM(Symbolic);
-  assert(Symbolic); // only symlinks are supported by SFTP
-  assert(FVersion >= 3); // symlinks are supported with SFTP version 3 and later
-  TSFTPPacket Packet(SSH_FXP_SYMLINK);
+  assert(FVersion >= 3); // links are supported with SFTP version 3 and later
+  bool UseLink = (FVersion >= 6);
+  bool UseHardlink = !Symbolic && !UseLink && FSupportsHardlink;
+  TSFTPPacket Packet(UseHardlink ? SSH_FXP_EXTENDED : (UseLink ? SSH_FXP_LINK : SSH_FXP_SYMLINK));
+  if (UseHardlink)
+  {
+    Packet.AddString(SFTP_EXT_HARDLINK);
+  }
 
-  bool Buggy =
-    (FTerminal->SessionData->SFTPBug[sbSymlink] == asOn) ||
-    ((FTerminal->SessionData->SFTPBug[sbSymlink] == asAuto) &&
-     (FSecureShell->IsOpenSSH() || FSecureShell->IsProFTPD()));
+  bool Buggy;
+  // OpenSSH hardlink extension always uses the "wrong" order
+  // as it's defined as such to mimic OpenSSH symlink bug
+  if (UseHardlink)
+  {
+    Buggy = true; //sic
+  }
+  else
+  {
+    if (FTerminal->SessionData->SFTPBug[sbSymlink] == asOn)
+    {
+      Buggy = true;
+      FTerminal->LogEvent(L"Forcing workaround for SFTP link bug");
+    }
+    else if (FTerminal->SessionData->SFTPBug[sbSymlink] == asOff)
+    {
+      Buggy = false;
+    }
+    else
+    {
+      if (UseLink)
+      {
+        if (FSecureShell->SshImplementation == sshiProFTPD)
+        {
+          // ProFTPD/mod_sftp followed OpenSSH symlink bug even for link implementation.
+          // This will be fixed with the next release with
+          // SSH version string bumbed to "mod_sftp/1.0.0"
+          // http://bugs.proftpd.org/show_bug.cgi?id=4080
+          UnicodeString ProFTPDVerStr = GetSessionInfo().SshImplementation;
+          CutToChar(ProFTPDVerStr, L'/', false);
+          int ProFTPDMajorVer = StrToIntDef(CutToChar(ProFTPDVerStr, L'.', false), 0);
+          Buggy = (ProFTPDMajorVer == 0);
+          if (Buggy)
+          {
+            FTerminal->LogEvent(L"We believe the server has SFTP link bug");
+          }
+        }
+        else
+        {
+          Buggy = false;
+        }
+      }
+      else
+      {
+        // ProFTPD/mod_sftp deliberately follows OpenSSH bug.
+        // Though we should get here with ProFTPD only when user forced
+        // SFTP version < 6 or when connecting to an ancient version of ProFTPD.
+        Buggy =
+          (FSecureShell->SshImplementation == sshiOpenSSH) ||
+          (FSecureShell->SshImplementation == sshiProFTPD);
+        if (Buggy)
+        {
+          FTerminal->LogEvent(L"We believe the server has SFTP symlink bug");
+        }
+      }
+    }
+  }
+
+  UnicodeString FinalPointTo = PointTo;
+  UnicodeString FinalFileName = Canonify(FileName);
+
+  if (!Symbolic)
+  {
+    FinalPointTo = Canonify(PointTo);
+  }
 
   if (!Buggy)
   {
-    Packet.AddPathString(Canonify(FileName), FUtfStrings);
-    Packet.AddPathString(PointTo, FUtfStrings);
+    Packet.AddPathString(FinalFileName, FUtfStrings);
+    Packet.AddPathString(FinalPointTo, FUtfStrings);
   }
   else
   {
-    FTerminal->LogEvent(L"We believe the server has SFTP symlink bug");
-    Packet.AddPathString(PointTo, FUtfStrings);
-    Packet.AddPathString(Canonify(FileName), FUtfStrings);
+    Packet.AddPathString(FinalPointTo, FUtfStrings);
+    Packet.AddPathString(FinalFileName, FUtfStrings);
+  }
+
+  if (UseLink)
+  {
+    Packet.AddBool(Symbolic);
   }
   SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS);
 }
@@ -3708,7 +3922,9 @@ TStrings * __fastcall TSFTPFileSystem::GetFixedPaths()
 void __fastcall TSFTPFileSystem::SpaceAvailable(const UnicodeString Path,
   TSpaceAvailable & ASpaceAvailable)
 {
-  if (SupportsExtension(SFTP_EXT_SPACE_AVAILABLE))
+  if (SupportsExtension(SFTP_EXT_SPACE_AVAILABLE) ||
+      // See comment in IsCapable
+      (FSecureShell->SshImplementation == sshiBitvise))
   {
     TSFTPPacket Packet(SSH_FXP_EXTENDED);
     Packet.AddString(SFTP_EXT_SPACE_AVAILABLE);
@@ -3720,7 +3936,14 @@ void __fastcall TSFTPFileSystem::SpaceAvailable(const UnicodeString Path,
     ASpaceAvailable.UnusedBytesOnDevice = Packet.GetInt64();
     ASpaceAvailable.BytesAvailableToUser = Packet.GetInt64();
     ASpaceAvailable.UnusedBytesAvailableToUser = Packet.GetInt64();
-    if (Packet.RemainingLength == 2)
+    // bytes-per-allocation-unit was added later to the protocol
+    // (revision 07, while the extension was defined already in rev 06),
+    // be tolerant
+    if (Packet.CanGetCardinal())
+    {
+      ASpaceAvailable.BytesPerAllocationUnit = Packet.GetCardinal();
+    }
+    else if (Packet.CanGetSmallCardinal())
     {
       // See http://bugs.proftpd.org/show_bug.cgi?id=4079
       FTerminal->LogEvent(L"Assuming ProFTPD/mod_sftp bug of 2-byte bytes-per-allocation-unit field");
@@ -3728,7 +3951,7 @@ void __fastcall TSFTPFileSystem::SpaceAvailable(const UnicodeString Path,
     }
     else
     {
-      ASpaceAvailable.BytesPerAllocationUnit = Packet.GetCardinal();
+      FTerminal->LogEvent(L"Missing bytes-per-allocation-unit field");
     }
   }
   else if (ALWAYS_TRUE(FSupportsStatVfsV2))
@@ -4603,7 +4826,13 @@ RawByteString __fastcall TSFTPFileSystem::SFTPOpenRemoteFile(
 
   bool SendSize =
     (Size >= 0) &&
-    FLAGSET(OpenType, SSH_FXF_CREAT | SSH_FXF_TRUNC);
+    FLAGSET(OpenType, SSH_FXF_CREAT | SSH_FXF_TRUNC) &&
+    // Particuarly VanDyke VShell (4.0.3) does not support SSH_FILEXFER_ATTR_ALLOCATION_SIZE
+    // (it fails open request when the attribute is included).
+    // It's SFTP-6 attribute, so support structure should be available.
+    // It's actually not with VShell. But VShell supports the SSH_FILEXFER_ATTR_ALLOCATION_SIZE.
+    // All servers should support SSH_FILEXFER_ATTR_SIZE (SFTP < 6)
+    (!FSupport->Loaded || FLAGSET(FSupport->AttributeMask, Packet.AllocationSizeAttribute(FVersion)));
   Packet.AddProperties(NULL, NULL, NULL, NULL, NULL,
     SendSize ? &Size : NULL, false, FVersion, FUtfStrings);
 
@@ -4760,7 +4989,7 @@ int __fastcall TSFTPFileSystem::SFTPOpenRemote(void * AOpenParams, void * /*Para
         // now we know that the file exists
 
         if (FTerminal->FileOperationLoopQuery(E, OperationProgress,
-              FMTLOAD(SFTP_OVERWRITE_FILE_ERROR, (OpenParams->RemoteFileName)),
+              FMTLOAD(SFTP_OVERWRITE_FILE_ERROR2, (OpenParams->RemoteFileName)),
               true, LoadStr(SFTP_OVERWRITE_DELETE_BUTTON)))
         {
           int Params = dfNoRecursive;
@@ -5434,8 +5663,14 @@ void __fastcall TSFTPFileSystem::SFTPSink(const UnicodeString FileName,
 
               assert(DataLen <= BlockSize);
               BlockBuf.Insert(0, reinterpret_cast<const char *>(DataPacket.GetNextData(DataLen)), DataLen);
+              DataPacket.DataConsumed(DataLen);
               OperationProgress->AddTransfered(DataLen);
 
+              if ((FVersion >= 6) && DataPacket.CanGetBool() && (Missing == 0))
+              {
+                Eof = DataPacket.GetBool();
+              }
+
               if (OperationProgress->AsciiTransfer)
               {
                 assert(!ResumeTransfer && !ResumeAllowed);

+ 2 - 0
source/core/SftpFileSystem.h

@@ -10,6 +10,7 @@ struct TSFTPSupport;
 class TSecureShell;
 //---------------------------------------------------------------------------
 enum TSFTPOverwriteMode { omOverwrite, omAppend, omResume };
+extern const int SFTPMaxVersion;
 //---------------------------------------------------------------------------
 class TSFTPFileSystem : public TCustomFileSystem
 {
@@ -103,6 +104,7 @@ protected:
   TStrings * FFixedPaths;
   unsigned long FMaxPacketSize;
   bool FSupportsStatVfsV2;
+  bool FSupportsHardlink;
 
   void __fastcall SendCustomReadFile(TSFTPPacket * Packet, TSFTPPacket * Response,
     unsigned long Flags);

+ 2 - 2
source/core/Terminal.cpp

@@ -1205,9 +1205,9 @@ unsigned int __fastcall TTerminal::QueryUserException(const UnicodeString Query,
       }
 
       // We know MoreMessages not to be NULL here,
-      // AppendExceptionStackTrace should never return true
+      // AppendExceptionStackTraceAndForget should never return true
       // (indicating it had to create the string list)
-      ALWAYS_FALSE(AppendExceptionStackTrace(MoreMessages));
+      ALWAYS_FALSE(AppendExceptionStackTraceAndForget(MoreMessages));
 
       TQueryParams HelpKeywordOverrideParams;
       if (Params != NULL)

+ 12 - 5
source/filezilla/ApiLog.cpp

@@ -67,11 +67,18 @@ BOOL CApiLog::InitLog(HWND hTargetWnd, int nLogMessage)
 	return TRUE;
 }
 
+bool CApiLog::LoggingMessageType(int nMessageType) const
+{
+  return
+    (nMessageType < FZ_LOG_APIERROR) ||
+    ((nMessageType-FZ_LOG_APIERROR) < m_pApiLogParent->m_nDebugLevel);
+}
+
 void CApiLog::LogMessage(int nMessageType, LPCTSTR pMsgFormat, ...) const
 {
 	ASSERT(nMessageType>=0 && nMessageType<=8);
 	ASSERT(m_hTargetWnd || m_pApiLogParent);
-	if (nMessageType>=FZ_LOG_APIERROR && (nMessageType-FZ_LOG_APIERROR)>=m_pApiLogParent->m_nDebugLevel)
+	if (!LoggingMessageType(nMessageType))
 		return;
 
 	va_list ap;
@@ -92,7 +99,7 @@ void CApiLog::LogMessageRaw(int nMessageType, LPCTSTR pMsg) const
 {
 	ASSERT(nMessageType>=0 && nMessageType<=8);
 	ASSERT(m_hTargetWnd || m_pApiLogParent);
-	if (nMessageType>=FZ_LOG_APIERROR && (nMessageType-FZ_LOG_APIERROR)>=m_pApiLogParent->m_nDebugLevel)
+	if (!LoggingMessageType(nMessageType))
 		return;
 
 #ifdef MPEXT
@@ -106,7 +113,7 @@ void CApiLog::LogMessage(int nMessageType, UINT nFormatID, ...) const
 {
 	ASSERT(nMessageType>=0 && nMessageType<=8);
 	ASSERT(m_hTargetWnd || m_pApiLogParent);
-	if (nMessageType>=FZ_LOG_APIERROR && (nMessageType-FZ_LOG_APIERROR)>=m_pApiLogParent->m_nDebugLevel)
+	if (!LoggingMessageType(nMessageType))
 		return;
 
 	CString str;
@@ -189,7 +196,7 @@ void CApiLog::SendLogMessage(int nMessageType, LPCTSTR pMsg) const
 	ASSERT(m_pApiLogParent);
 	ASSERT(m_pApiLogParent->m_hTargetWnd == 0);
 	ASSERT(m_pApiLogParent->m_nLogMessage == 0);
-	if (nMessageType>=FZ_LOG_APIERROR && (nMessageType-FZ_LOG_APIERROR)>=m_pApiLogParent->m_nDebugLevel)
+	if (!LoggingMessageType(nMessageType))
 		return;
 	//Displays a message in the message log	
 	t_ffam_statusmessage *pStatus = new t_ffam_statusmessage;
@@ -210,7 +217,7 @@ void CApiLog::SendLogMessage(int nMessageType, LPCTSTR pMsg) const
 		ASSERT(m_pApiLogParent);
 		ASSERT(m_pApiLogParent->m_hTargetWnd);
 		ASSERT(m_pApiLogParent->m_nLogMessage);
-		if (nMessageType>=FZ_LOG_APIERROR && (nMessageType-FZ_LOG_APIERROR)>=m_pApiLogParent->m_nDebugLevel)
+		if (!LoggingMessageType(nMessageType))
 			return;
 	}
 	//Displays a message in the message log	

+ 2 - 0
source/filezilla/ApiLog.h

@@ -36,6 +36,8 @@ public:
 	BOOL InitLog(HWND hTargerWnd, int nLogMessage);
 	BOOL InitLog(CApiLog *pParent);
 
+	bool LoggingMessageType(int nMessageType) const;
+
 	void LogMessage(int nMessageType, LPCTSTR pMsgFormat, ...) const;
 	void LogMessageRaw(int nMessageType, LPCTSTR pMsg) const;
 	void LogMessage(int nMessageType, UINT nFormatID, ...) const;

+ 1 - 0
source/filezilla/AsyncSocketEx.h

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

+ 9 - 0
source/filezilla/AsyncSocketExLayer.cpp

@@ -1041,3 +1041,12 @@ void CAsyncSocketExLayer::LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat)
 	else
 		m_pOwnerSocket->LogSocketMessage(nMessageType, pMsgFormat);
 }
+
+bool CAsyncSocketExLayer::LoggingSocketMessage(int nMessageType)
+{
+	if (m_pPrevLayer)
+		return m_pPrevLayer->LoggingSocketMessage(nMessageType);
+	else
+		return m_pOwnerSocket->LoggingSocketMessage(nMessageType);
+}
+

+ 1 - 0
source/filezilla/AsyncSocketExLayer.h

@@ -141,6 +141,7 @@ protected:
 	bool TryNextProtocol();
 
 	void LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat);
+	bool LoggingSocketMessage(int nMessageType);
 
 private:
 	//Layer state can't be set directly from derived classes

+ 16 - 5
source/filezilla/AsyncSslSocketLayer.cpp

@@ -816,6 +816,20 @@ int CAsyncSslSocketLayer::InitSSLConnection(bool clientMode,
 		return SSL_FAILURE_INITSSL;
 	}
 
+#ifdef _DEBUG
+	if ((main == NULL) && LoggingSocketMessage(FZ_LOG_INFO))
+	{
+		USES_CONVERSION;
+		LogSocketMessage(FZ_LOG_INFO, _T("Supported ciphersuites:"));
+		STACK_OF(SSL_CIPHER) * ciphers = SSL_get_ciphers(m_ssl);
+		for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); i++)
+		{
+			SSL_CIPHER * cipher = sk_SSL_CIPHER_value(ciphers, i);
+			LogSocketMessage(FZ_LOG_INFO, A2CT(cipher->name));
+		}
+	}
+#endif
+
 	//Add current instance to list of active instances
 	t_SslLayerList *tmp = m_pSslLayerList;
 	m_pSslLayerList = new t_SslLayerList;
@@ -849,11 +863,8 @@ int CAsyncSslSocketLayer::InitSSLConnection(bool clientMode,
 
 	//Init SSL connection
 	void *ssl_sessionid = NULL;
-	{
-		USES_CONVERSION;
-		m_Main = main;
-		m_sessionreuse = sessionreuse;
-	}
+	m_Main = main;
+	m_sessionreuse = sessionreuse;
 	if ((m_Main != NULL) && m_sessionreuse)
 	{
 		if (m_Main->m_sessionid != NULL)

+ 6 - 0
source/filezilla/ControlSocket.cpp

@@ -521,3 +521,9 @@ void CControlSocket::LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat)
 {
 	LogMessage(nMessageType, pMsgFormat);
 }
+
+bool CControlSocket::LoggingSocketMessage(int nMessageType)
+{
+	return LoggingMessageType(nMessageType);
+}
+

+ 1 - 0
source/filezilla/ControlSocket.h

@@ -165,6 +165,7 @@ protected:
 	//End Speed limit
 	
 	virtual void LogSocketMessage(int nMessageType, LPCTSTR pMsgFormat);
+	virtual bool LoggingSocketMessage(int nMessageType);
 
 public:
 	struct t_operation

+ 10 - 10
source/forms/CustomScpExplorer.cpp

@@ -1044,7 +1044,7 @@ bool __fastcall TCustomScpExplorerForm::CopyParamDialog(
         try
         {
           FileList1->AddObject(FileList->Strings[Index], FileList->Objects[Index]);
-          AddQueueItem(Direction, FileList1, TargetDirectory, CopyParam, Params);
+          AddQueueItem(Queue, Direction, FileList1, TargetDirectory, CopyParam, Params);
         }
         __finally
         {
@@ -1054,7 +1054,7 @@ bool __fastcall TCustomScpExplorerForm::CopyParamDialog(
     }
     else
     {
-      AddQueueItem(Direction, FileList, TargetDirectory, CopyParam, Params);
+      AddQueueItem(Queue, Direction, FileList, TargetDirectory, CopyParam, Params);
     }
     Result = false;
 
@@ -1074,7 +1074,7 @@ void __fastcall TCustomScpExplorerForm::ClearTransferSourceSelection(TTransferDi
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::AddQueueItem(
-  TTransferDirection Direction, TStrings * FileList,
+  TTerminalQueue * Queue, TTransferDirection Direction, TStrings * FileList,
   const UnicodeString TargetDirectory, const TCopyParamType & CopyParam,
   int Params)
 {
@@ -1106,10 +1106,10 @@ void __fastcall TCustomScpExplorerForm::AddQueueItem(
     QueueItem = new TDownloadQueueItem(Terminal, FileList, TargetDirectory,
       &CopyParam, Params, SingleFile);
   }
-  AddQueueItem(QueueItem);
+  AddQueueItem(Queue, QueueItem);
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::AddQueueItem(TQueueItem * QueueItem)
+void __fastcall TCustomScpExplorerForm::AddQueueItem(TTerminalQueue * Queue, TQueueItem * QueueItem)
 {
   if (Queue->IsEmpty)
   {
@@ -1876,8 +1876,8 @@ void __fastcall TCustomScpExplorerForm::DirViewContextPopupDefaultItem(
       // when resolving links is disabled, default action is to enter the directory,
       // no matter what DoubleClickAction is configured to
       ((Side != osRemote) || Terminal->ResolvingSymlinks) &&
-      // Can only Open/Edit files, but can Copy even directories
-      (((DoubleClickAction != dcaOpen) && (DoubleClickAction != dcaEdit)) ||
+      // Can only Edit files, but can Open/Copy even directories
+      ((DoubleClickAction != dcaEdit) ||
        !DView->ItemIsDirectory(DView->ItemFocused)))
   {
     Item->Options = O << tboDefault;
@@ -2065,7 +2065,7 @@ bool __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Oper
 
               Configuration->Usage->Inc("MovesToBackground");
 
-              AddQueueItem(Direction, FileList, TargetDirectory, CopyParam, Params);
+              AddQueueItem(Queue, Direction, FileList, TargetDirectory, CopyParam, Params);
               ClearTransferSourceSelection(Direction);
             }
 
@@ -3010,13 +3010,13 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(const UnicodeString
     CopyParam.ReplaceInvalidChars = true;
     CopyParam.IncludeFileMask = TFileMasks();
 
-    assert(Queue != NULL);
+    assert(Data->Queue != NULL);
 
     int Params = cpNoConfirmation | cpTemporary;
     TQueueItem * QueueItem = new TUploadQueueItem(Data->Terminal, FileList,
       Data->RemoteDirectory, &CopyParam, Params, true);
     QueueItem->CompleteEvent = UploadCompleteEvent;
-    AddQueueItem(QueueItem);
+    AddQueueItem(Data->Queue, QueueItem);
   }
   __finally
   {

+ 2 - 2
source/forms/CustomScpExplorer.h

@@ -263,10 +263,10 @@ private:
   void __fastcall QueueSplitterDblClick(TObject * Sender);
   void __fastcall ApplicationMinimize(TObject * Sender);
   void __fastcall ApplicationRestore(TObject * Sender);
-  void __fastcall AddQueueItem(TTransferDirection Direction,
+  void __fastcall AddQueueItem(TTerminalQueue * Queue, TTransferDirection Direction,
     TStrings * FileList, const UnicodeString TargetDirectory,
     const TCopyParamType & CopyParam, int Params);
-  void __fastcall AddQueueItem(TQueueItem * QueueItem);
+  void __fastcall AddQueueItem(TTerminalQueue * Queue, TQueueItem * QueueItem);
   void __fastcall ClearTransferSourceSelection(TTransferDirection Direction);
   void __fastcall SessionsDDDragOver(int KeyState, const TPoint & Point, int & Effect);
   void __fastcall SessionsDDProcessDropped(TObject * Sender, int KeyState, const TPoint & Point, int Effect);

+ 3 - 1
source/forms/MessageDlg.cpp

@@ -296,9 +296,10 @@ UnicodeString __fastcall TMessageForm::GetFormText()
 UnicodeString __fastcall TMessageForm::GetReportText()
 {
   UnicodeString Text = MessageText;
+  Text = Text.TrimRight();
   if (MessageMemo != NULL)
   {
-    Text += L"\n" + MessageMemo->Text;
+    Text += L"\n\n" + MessageMemo->Text;
   }
   // Currently we use browser for updates box only and it has help context,
   // and does not have Report button, so we cannot get here.
@@ -1008,6 +1009,7 @@ TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg,
   Panel->Caption = L"";
 
   UnicodeString BodyMsg = Msg;
+  BodyMsg = RemoveInteractiveMsgTag(BodyMsg);
   UnicodeString MainMsg;
   if (ExtractMainInstructions(BodyMsg, MainMsg))
   {

+ 1 - 2
source/forms/NonVisual.cpp

@@ -170,8 +170,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPD(CurrentEditWithAction, EnabledSelectedFileOperation &&
     !WinConfiguration->DisableOpenEdit)
   UPD(CurrentOpenAction, EnabledFocusedOperation &&
-    !WinConfiguration->DisableOpenEdit &&
-    !DirView(osCurrent)->ItemIsDirectory(DirView(osCurrent)->ItemFocused))
+    !WinConfiguration->DisableOpenEdit)
   UPDEX1(CurrentAddEditLinkContextAction, ScpExplorer->CanAddEditLink(osCurrent),
     ((TAction *)Action)->Visible = ScpExplorer->LinkFocused())
   UPD(NewLinkAction, ScpExplorer->CanAddEditLink(osCurrent))

+ 7 - 4
source/forms/SiteAdvanced.dfm

@@ -2,7 +2,7 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
   Left = 351
   Top = 167
   HelpType = htKeyword
-  HelpKeyword = 'ui_site_advanced'
+  HelpKeyword = 'ui_login_advanced'
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderStyle = bsDialog
   Caption = 'Advanced Site Settings'
@@ -421,9 +421,9 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
           object Label10: TLabel
             Left = 12
             Top = 20
-            Width = 230
+            Width = 211
             Height = 13
-            Caption = '&Reverses order of symlink command arguments:'
+            Caption = '&Reverses order of link command arguments:'
             FocusControl = SFTPBugSymlinkCombo
           end
           object Label36: TLabel
@@ -494,7 +494,8 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
               '2'
               '3'
               '4'
-              '5')
+              '5'
+              '6')
           end
           object SftpServerEdit: TComboBox
             Left = 149
@@ -2243,6 +2244,8 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
         end
       end
       object NoteSheet: TTabSheet
+        HelpType = htKeyword
+        HelpKeyword = 'ui_login_note'
         Caption = 'Note'
         ImageIndex = 14
         TabVisible = False

+ 7 - 5
source/forms/Synchronize.cpp

@@ -119,10 +119,7 @@ void __fastcall TSynchronizeDialog::FeedSynchronizeError(
   const UnicodeString & Message, TStrings * MoreMessages, TQueryType Type,
   const UnicodeString & HelpKeyword)
 {
-  UnicodeString AMessage = Message;
-  AMessage = ReplaceStr(AMessage, L"\r", L"");
-  AMessage = ReplaceStr(AMessage, L"\n", L" ");
-  DoLogInternal(slContinuedError, AMessage, MoreMessages, Type, HelpKeyword);
+  DoLogInternal(slContinuedError, Message, MoreMessages, Type, HelpKeyword);
 }
 //---------------------------------------------------------------------------
 void __fastcall TSynchronizeDialog::UpdateControls()
@@ -355,7 +352,12 @@ void __fastcall TSynchronizeDialog::DoLogInternal(
     LogItemData->HelpKeyword = HelpKeyword;
 
     Item->Caption = Now().TimeString();
-    Item->SubItems->Add(Message);
+
+    UnicodeString UnformattedMessage = UnformatMessage(Message);
+    UnformattedMessage = ReplaceStr(UnformattedMessage, L"\r", L"");
+    UnformattedMessage = ReplaceStr(UnformattedMessage, L"\n", L" ");
+
+    Item->SubItems->Add(UnformattedMessage);
     Item->MakeVisible(false);
     while (LogView->Items->Count > MaxLogItems)
     {

+ 5 - 1
source/resource/TextsCore.h

@@ -169,7 +169,7 @@
 #define KEY_TYPE_UNKNOWN        243
 #define KEY_TYPE_UNSUPPORTED    244
 #define KEY_TYPE_DIFFERENT_SSH  245
-#define SFTP_OVERWRITE_FILE_ERROR 246
+#define SFTP_OVERWRITE_FILE_ERROR2 246
 #define SFTP_OVERWRITE_DELETE_BUTTON 247
 #define SPACE_AVAILABLE_ERROR   248
 #define TUNNEL_NO_FREE_PORT     249
@@ -233,6 +233,9 @@
 #define INVALID_URL             708
 #define PROXY_AUTHENTICATION_FAILED 709
 #define CONFIGURED_KEY_NOT_MATCH 710
+#define SFTP_STATUS_OWNER_INVALID 711
+#define SFTP_STATUS_GROUP_INVALID 712
+#define SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK 713
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301
@@ -433,6 +436,7 @@
 #define EXPAT_URL               618
 #define PUTTY_LICENSE_URL       625
 #define MAIN_MSG_TAG            631
+#define INTERACTIVE_MSG_TAG     632
 
 // 7xxx used by errors as secondary sequence
 

+ 6 - 2
source/resource/TextsCore1.rc

@@ -106,7 +106,7 @@ BEGIN
   SFTP_PACKET_TOO_BIG_INIT_EXPLAIN, "%s\n \nThe error is typically caused by message printed from startup script (like .profile). The message may start with %s."
   PRESERVE_TIME_PERM_ERROR3, "**Upload of file '%s' was successful, but error occurred while setting the permissions and/or timestamp.**\n\nIf the problem persists, turn off setting permissions or preserving timestamp. Alternatively you can turn on 'Ignore permission errors' option."
   ACCESS_VIOLATION_ERROR3, "Invalid access to memory."
-  SFTP_STATUS_NO_SPACE_ON_FILESYSTEM, "There is no free space on the filesystem."
+  SFTP_STATUS_NO_SPACE_ON_FILESYSTEM, "There is insufficient free space on the filesystem."
   SFTP_STATUS_QUOTA_EXCEEDED, "Operation cannot be completed because it would exceed the users storage quota."
   SFTP_STATUS_UNKNOWN_PRINCIPAL, "Principal (%s) is unknown to the server."
   COPY_FILE_ERROR, "Error copying file '%s' to '%s'."
@@ -140,7 +140,7 @@ BEGIN
   KEY_TYPE_UNKNOWN, "File '%s' does not exist or it does not contain private key in known format."
   KEY_TYPE_UNSUPPORTED, "**Private key file '%s' contains key in %s format. WinSCP supports only PuTTY format.**\n \nYou can use PuTTYgen tool to convert your private key file."
   KEY_TYPE_DIFFERENT_SSH, "Private key file '%s' contains key in %s format. It does not follow your preferred SSH protocol version."
-  SFTP_OVERWRITE_FILE_ERROR, "Cannot overwrite remote file '%s'.\n \nPress 'Delete' to delete the file and create new one instead of overwriting it."
+  SFTP_OVERWRITE_FILE_ERROR2, "Cannot overwrite remote file '%s'.$$\n \nPress 'Delete' to delete the file and create new one instead of overwriting it.$$"
   SFTP_OVERWRITE_DELETE_BUTTON, "&Delete"
   SPACE_AVAILABLE_ERROR, "Error checking space available for path '%s'."
   TUNNEL_NO_FREE_PORT, "Cannot find free local listening port number for tunnel in range %d to %d."
@@ -204,6 +204,9 @@ BEGIN
   INVALID_URL, "Invalid URL \"%s\"."
   PROXY_AUTHENTICATION_FAILED, "Proxy authentication failed."
   CONFIGURED_KEY_NOT_MATCH, "Host key does not match configured key \"%s\"!"
+  SFTP_STATUS_OWNER_INVALID, "The name specified can not be assigned as an owner 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."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"
@@ -404,4 +407,5 @@ BEGIN
   EXPAT_URL, "http://www.libexpat.org/"
   PUTTY_LICENSE_URL, "http://www.chiark.greenend.org.uk/~sgtatham/putty/licence.html"
   MAIN_MSG_TAG, "**"
+  INTERACTIVE_MSG_TAG, "$$"
 END

+ 1 - 1
source/resource/TextsWin2.rc

@@ -21,7 +21,7 @@ BEGIN
 "G:%APP% site|workspace|folder\n"
 "G:%APP% (sftp|scp|ftp|http[s])://[user[:password]@]host[:port][/path/[file]]\n"
 "G:%APP% [mysession] /newinstance\n"
-"G:%APP% [mysession] /edit file\n"
+"G:%APP% [mysession] /edit <file>\n"
 "G:%APP% [mysession] /synchronize [local_dir] [remote_dir] [/defaults]\n"
 "G:%APP% [mysession] /keepuptodate [local_dir] [remote_dir] [/defaults]\n"
 "G:%APP% [mysession] [/privatekey=<key>] [/hostkey=<fingerprint>]\n"

+ 1 - 0
source/windows/ConsoleRunner.cpp

@@ -1266,6 +1266,7 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
   }
 
   AQuery = UnformatMessage(AQuery);
+  AQuery = RemoveInteractiveMsgTag(AQuery);
 
   ApplyTabs(AQuery, L' ', NULL, NULL);
 

+ 7 - 2
source/windows/Setup.cpp

@@ -462,7 +462,6 @@ static void __fastcall UnregisterAsUrlHandlers(const UnicodeString & Prefix, boo
 {
   UnregisterAsUrlHandler(Prefix + SftpProtocol, UnregisterProtocol);
   UnregisterAsUrlHandler(Prefix + ScpProtocol, UnregisterProtocol);
-  // add WebDAV
 }
 //---------------------------------------------------------------------------
 static const UnicodeString GenericUrlHandler(L"WinSCP.Url");
@@ -564,6 +563,9 @@ static void __fastcall RegisterProtocolsForDefaultPrograms(HKEY RootKey)
   RegisterProtocolForDefaultPrograms(RootKey, FtpsProtocol);
   RegisterProtocolForDefaultPrograms(RootKey, SftpProtocol);
   RegisterProtocolForDefaultPrograms(RootKey, ScpProtocol);
+  // deliberately not including WebDAV/http,
+  // it's unlikely that anyone would like to change http handler
+  // to non-browser application
 }
 //---------------------------------------------------------------------------
 static void __fastcall UnregisterProtocolsForDefaultPrograms(HKEY RootKey, bool ForceHandlerUnregistration)
@@ -618,7 +620,8 @@ void __fastcall RegisterForDefaultProtocols()
   RegisterAsNonBrowserUrlHandler(WinSCPProtocolPrefix);
   RegisterAsUrlHandler(WinSCPProtocolPrefix + FtpProtocol.UpperCase());
   RegisterAsUrlHandler(WinSCPProtocolPrefix + FtpsProtocol.UpperCase());
-  // add WebDAV
+  RegisterAsUrlHandler(WinSCPProtocolPrefix + WebDAVProtocol.UpperCase());
+  RegisterAsUrlHandler(WinSCPProtocolPrefix + WebDAVSProtocol.UpperCase());
 
   NotifyChangedAssociations();
 }
@@ -629,6 +632,8 @@ void __fastcall UnregisterForProtocols()
   UnregisterAsUrlHandlers(WinSCPProtocolPrefix, true);
   UnregisterAsUrlHandler(WinSCPProtocolPrefix + FtpProtocol.UpperCase(), true);
   UnregisterAsUrlHandler(WinSCPProtocolPrefix + FtpsProtocol.UpperCase(), true);
+  UnregisterAsUrlHandler(WinSCPProtocolPrefix + WebDAVProtocol.UpperCase(), true);
+  UnregisterAsUrlHandler(WinSCPProtocolPrefix + WebDAVSProtocol.UpperCase(), true);
 
   UnregisterProtocolsForDefaultPrograms(HKEY_CURRENT_USER, false);
   UnregisterProtocolsForDefaultPrograms(HKEY_LOCAL_MACHINE, false);

+ 41 - 31
source/windows/WinInterface.cpp

@@ -538,30 +538,35 @@ static TStrings * __fastcall StackInfoListToStrings(
   return StackTrace.release();
 }
 //---------------------------------------------------------------------------
-bool __fastcall AppendExceptionStackTrace(TStrings *& MoreMessages)
+static std::unique_ptr<TCriticalSection> StackTraceCriticalSection(new TCriticalSection());
+typedef std::map<DWORD, TStrings *> TStackTraceMap;
+static TStackTraceMap StackTraceMap;
+//---------------------------------------------------------------------------
+bool __fastcall AppendExceptionStackTraceAndForget(TStrings *& MoreMessages)
 {
-  TJclStackInfoList * StackInfoList = JclLastExceptStackList();
-  std::unique_ptr<TStrings> OwnedMoreMessages;
   bool Result = false;
-  if (StackInfoList != NULL)
+
+  TGuard Guard(StackTraceCriticalSection.get());
+
+  TStackTraceMap::iterator Iterator = StackTraceMap.find(GetCurrentThreadId());
+  if (Iterator != StackTraceMap.end())
   {
+    std::unique_ptr<TStrings> OwnedMoreMessages;
     if (MoreMessages == NULL)
     {
       OwnedMoreMessages.reset(new TStringList());
       MoreMessages = OwnedMoreMessages.get();
       Result = true;
     }
-    std::unique_ptr<TStrings> StackTrace(StackInfoListToStrings(StackInfoList));
     if (!MoreMessages->Text.IsEmpty())
     {
       MoreMessages->Text = MoreMessages->Text + "\n";
     }
     MoreMessages->Text = MoreMessages->Text + LoadStr(STACK_TRACE) + "\n";
-    MoreMessages->AddStrings(StackTrace.get());
+    MoreMessages->AddStrings(Iterator->second);
 
-    // this chains so that JclLastExceptStackList() returns NULL the next time
-    // for the current thread
-    delete StackInfoList;
+    delete Iterator->second;
+    StackTraceMap.erase(Iterator);
 
     OwnedMoreMessages.release();
   }
@@ -587,32 +592,11 @@ unsigned int __fastcall ExceptionMessageDialog(Exception * E, TQueryType Type,
   HelpKeyword = MergeHelpKeyword(HelpKeyword, GetExceptionHelpKeyword(E));
 
   std::unique_ptr<TStrings> OwnedMoreMessages;
-  if (AppendExceptionStackTrace(MoreMessages))
+  if (AppendExceptionStackTraceAndForget(MoreMessages))
   {
     OwnedMoreMessages.reset(MoreMessages);
   }
 
-  TJclStackInfoList * StackInfoList = JclLastExceptStackList();
-  if (StackInfoList != NULL)
-  {
-    if (MoreMessages == NULL)
-    {
-      OwnedMoreMessages.reset(new TStringList());
-      MoreMessages = OwnedMoreMessages.get();
-    }
-    std::unique_ptr<TStrings> StackTrace(StackInfoListToStrings(StackInfoList));
-    if (!MoreMessages->Text.IsEmpty())
-    {
-      MoreMessages->Text = MoreMessages->Text + "\n";
-    }
-    MoreMessages->Text = MoreMessages->Text + LoadStr(STACK_TRACE) + "\n";
-    MoreMessages->AddStrings(StackTrace.get());
-
-    // this chains so that JclLastExceptStackList() returns NULL the next time
-    // for the current thread
-    delete StackInfoList;
-  }
-
   return MoreMessageDialog(
     FORMAT(MessageFormat.IsEmpty() ? UnicodeString(L"%s") : MessageFormat, (Message)),
     MoreMessages, Type, Answers, HelpKeyword, Params);
@@ -657,6 +641,32 @@ static void __fastcall DoExceptNotify(TObject * ExceptObj, void * ExceptAddr,
     if ((E != NULL) && IsInternalException(E)) // optimization
     {
       DoExceptionStackTrace(ExceptObj, ExceptAddr, OSException, BaseOfStack);
+
+      TJclStackInfoList * StackInfoList = JclLastExceptStackList();
+
+      if (ALWAYS_TRUE(StackInfoList != NULL))
+      {
+        std::unique_ptr<TStrings> StackTrace(StackInfoListToStrings(StackInfoList));
+
+        DWORD ThreadID = GetCurrentThreadId();
+
+        TGuard Guard(StackTraceCriticalSection.get());
+
+        TStackTraceMap::iterator Iterator = StackTraceMap.find(ThreadID);
+        if (Iterator != StackTraceMap.end())
+        {
+          Iterator->second->Add(L"");
+          Iterator->second->AddStrings(StackTrace.get());
+        }
+        else
+        {
+          StackTraceMap.insert(std::make_pair(ThreadID, StackTrace.release()));
+        }
+
+        // this chains so that JclLastExceptStackList() returns NULL the next time
+        // for the current thread
+        delete StackInfoList;
+      }
     }
   }
 }