Browse Source

With SFTP protocol files can be streamed from stdin in scripting (part of Bug 1738)

https://winscp.net/tracker/1738

Source commit: 9c5fd31853ec9bea872e1598abebc92ff507d693
Martin Prikryl 5 năm trước cách đây
mục cha
commit
6e5c55fab9

+ 2 - 1
dotnet/internal/ConsoleCommStruct.cs

@@ -12,7 +12,8 @@ namespace WinSCP
         public uint InputType;
         public uint OutputType;
         public bool WantsProgress; // since version 6
-        public bool UseStdErr; // since version 10
+        public bool UseStdErr; // implies "binary output") since version 10
+        public bool BinaryInput; // since version 10
     }
 
     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

+ 4 - 2
source/console/Console.h

@@ -21,7 +21,8 @@ struct TConsoleCommStruct
     unsigned int InputType;
     unsigned int OutputType;
     bool WantsProgress; // since version 6
-    bool UseStdErr; // since version 10
+    bool UseStdErr; // (implies "binary output") since version 10
+    bool BinaryInput; // since version 10
   };
 
   struct TPrintEvent
@@ -75,11 +76,12 @@ struct TConsoleCommStruct
   {
     unsigned char Data[20480];
     size_t Len;
+    bool Error;
   };
 
   size_t Size;
   int Version;
-  enum { NONE, PRINT, INPUT, CHOICE, TITLE, INIT, PROGRESS, TRANSFEROUT } Event;
+  enum { NONE, PRINT, INPUT, CHOICE, TITLE, INIT, PROGRESS, TRANSFEROUT, TRANSFERIN } Event;
 
   union
   {

+ 25 - 0
source/console/Main.cpp

@@ -791,6 +791,11 @@ inline void ProcessInitEvent(TConsoleCommStruct::TInitEvent& Event)
     setmode(fileno(stdout), O_BINARY);
   }
 
+  if (Event.BinaryInput)
+  {
+    setmode(fileno(stdin), O_BINARY);
+  }
+
   OutputType = GetFileType(ConsoleOutput);
   // Until now we should not have printed anything.
   // Only in case of a fatal failure, we might have printed a pure ASCII error messages (and never got here).
@@ -815,6 +820,22 @@ inline void ProcessTransferOutEvent(TConsoleCommStruct::TTransferEvent& Event)
   fwrite(Event.Data, 1, Event.Len, stdout);
 }
 //---------------------------------------------------------------------------
+inline void ProcessTransferInEvent(TConsoleCommStruct::TTransferEvent& Event)
+{
+  size_t Read = fread(Event.Data, 1, Event.Len, stdin);
+  if (Read != Event.Len)
+  {
+    if (ferror(stdin))
+    {
+      Event.Error = true;
+    }
+    else
+    {
+      Event.Len = Read;
+    }
+  }
+}
+//---------------------------------------------------------------------------
 void ProcessEvent(HANDLE ResponseEvent, HANDLE FileMapping)
 {
   TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
@@ -851,6 +872,10 @@ void ProcessEvent(HANDLE ResponseEvent, HANDLE FileMapping)
         ProcessTransferOutEvent(CommStruct->TransferEvent);
         break;
 
+      case TConsoleCommStruct::TRANSFERIN:
+        ProcessTransferInEvent(CommStruct->TransferEvent);
+        break;
+
       default:
         throw runtime_error("Unknown event");
     }

+ 12 - 0
source/core/Common.cpp

@@ -1404,6 +1404,18 @@ TSearchRecSmart::TSearchRecSmart()
   FLastWriteTimeSource.dwHighDateTime = 0;
 }
 //---------------------------------------------------------------------------
+void TSearchRecSmart::Clear()
+{
+  Size = 0;
+  Attr = 0;
+  Name = TFileName();
+  ExcludeAttr = 0;
+  FindHandle = 0;
+  memset(&FindData, 0, sizeof(FindData));
+  FLastWriteTimeSource.dwLowDateTime = 0;
+  FLastWriteTimeSource.dwHighDateTime = 0;
+}
+//---------------------------------------------------------------------------
 TDateTime TSearchRecSmart::GetLastWriteTime() const
 {
   if ((FindData.ftLastWriteTime.dwLowDateTime != FLastWriteTimeSource.dwLowDateTime) ||

+ 1 - 0
source/core/Common.h

@@ -188,6 +188,7 @@ struct TSearchRecSmart : public TSearchRec
 {
 public:
   TSearchRecSmart();
+  void Clear();
   TDateTime GetLastWriteTime() const;
   bool IsRealFile() const;
   bool IsDirectory() const;

+ 6 - 0
source/core/CopyParam.cpp

@@ -60,6 +60,7 @@ void __fastcall TCopyParamType::Default()
   Size = -1;
   OnceDoneOperation = odoIdle;
   OnTransferOut = NULL;
+  OnTransferIn = NULL;
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TCopyParamType::GetInfoStr(
@@ -577,6 +578,7 @@ void __fastcall TCopyParamType::Assign(const TCopyParamType * Source)
   COPY(Size);
   COPY(OnceDoneOperation);
   COPY(OnTransferOut);
+  COPY(OnTransferIn);
   #undef COPY
 }
 //---------------------------------------------------------------------------
@@ -903,6 +905,7 @@ void __fastcall TCopyParamType::Load(THierarchicalStorage * Storage)
   Size = -1;
   OnceDoneOperation = odoIdle;
   OnTransferOut = NULL;
+  OnTransferIn = NULL;
 }
 //---------------------------------------------------------------------------
 void __fastcall TCopyParamType::Save(THierarchicalStorage * Storage, const TCopyParamType * Defaults) const
@@ -952,6 +955,7 @@ void __fastcall TCopyParamType::Save(THierarchicalStorage * Storage, const TCopy
   DebugAssert(Size < 0);
   DebugAssert(OnceDoneOperation == odoIdle);
   DebugAssert(OnTransferOut == NULL);
+  DebugAssert(OnTransferIn == NULL);
 }
 //---------------------------------------------------------------------------
 #define C(Property) (Property == rhp.Property)
@@ -960,9 +964,11 @@ bool __fastcall TCopyParamType::operator==(const TCopyParamType & rhp) const
   DebugAssert(FTransferSkipList.get() == NULL);
   DebugAssert(FTransferResumeFile.IsEmpty());
   DebugAssert(OnTransferOut == NULL);
+  DebugAssert(OnTransferIn == NULL);
   DebugAssert(rhp.FTransferSkipList.get() == NULL);
   DebugAssert(rhp.FTransferResumeFile.IsEmpty());
   DebugAssert(rhp.OnTransferOut == NULL);
+  DebugAssert(rhp.OnTransferIn == NULL);
   return
     C(AddXToDirectories) &&
     C(AsciiFileMask) &&

+ 2 - 0
source/core/CopyParam.h

@@ -72,6 +72,7 @@ private:
   __int64 FSize;
   TOnceDoneOperation FOnceDoneOperation;
   TTransferOutEvent FOnTransferOut;
+  TTransferInEvent FOnTransferIn;
   static const wchar_t TokenPrefix = L'%';
   static const wchar_t NoReplacement = wchar_t(false);
   static const wchar_t TokenReplacement = wchar_t(true);
@@ -149,6 +150,7 @@ public:
   __property __int64 Size = { read = FSize, write = FSize };
   __property TOnceDoneOperation OnceDoneOperation = { read = FOnceDoneOperation, write = FOnceDoneOperation };
   __property TTransferOutEvent OnTransferOut = { read = FOnTransferOut, write = FOnTransferOut };
+  __property TTransferInEvent OnTransferIn = { read = FOnTransferIn, write = FOnTransferIn };
 };
 //---------------------------------------------------------------------------
 unsigned long __fastcall GetSpeedLimit(const UnicodeString & Text);

+ 20 - 5
source/core/FileBuffer.cpp

@@ -58,6 +58,15 @@ void __fastcall TFileBuffer::SetMemory(TMemoryStream * value)
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TFileBuffer::ProcessRead(DWORD Len, DWORD Result)
+{
+  if (Result != Len)
+  {
+    Size = Size - Len + Result;
+  }
+  FMemory->Seek(Result, soCurrent);
+}
+//---------------------------------------------------------------------------
 DWORD __fastcall TFileBuffer::ReadStream(TStream * Stream, const DWORD Len, bool ForceLen)
 {
   DWORD Result;
@@ -75,11 +84,7 @@ DWORD __fastcall TFileBuffer::ReadStream(TStream * Stream, const DWORD Len, bool
     {
       Result = Stream->Read(Data + Position, Len);
     }
-    if (Result != Len)
-    {
-      Size = Size - Len + Result;
-    }
-    FMemory->Seek(Result, soCurrent);
+    ProcessRead(Len, Result);
   }
   catch(EReadError &)
   {
@@ -94,6 +99,16 @@ DWORD __fastcall TFileBuffer::LoadStream(TStream * Stream, const DWORD Len, bool
   return ReadStream(Stream, Len, ForceLen);
 }
 //---------------------------------------------------------------------------
+DWORD __fastcall TFileBuffer::LoadFromIn(TTransferInEvent OnTransferIn, TObject * Sender, DWORD Len)
+{
+  FMemory->Seek(0, soFromBeginning);
+  DebugAssert(Position == 0);
+  Size = Position + Len;
+  size_t Result = OnTransferIn(Sender, reinterpret_cast<unsigned char *>(Data) + Position, Len);
+  ProcessRead(Len, Result);
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall TFileBuffer::Convert(char * Source, char * Dest, int Params,
   bool & Token)
 {

+ 3 - 0
source/core/FileBuffer.h

@@ -10,6 +10,7 @@ const int cpRemoveCtrlZ = 0x01;
 const int cpRemoveBOM =   0x02;
 //---------------------------------------------------------------------------
 typedef void __fastcall (__closure *TTransferOutEvent)(TObject * Sender, const unsigned char * Data, size_t Len);
+typedef size_t __fastcall (__closure *TTransferInEvent)(TObject * Sender, unsigned char * Data, size_t Len);
 //---------------------------------------------------------------------------
 class TFileBuffer
 {
@@ -24,6 +25,7 @@ public:
   void __fastcall Delete(int Index, int Len);
   DWORD __fastcall LoadStream(TStream * Stream, const DWORD Len, bool ForceLen);
   DWORD __fastcall ReadStream(TStream * Stream, const DWORD Len, bool ForceLen);
+  DWORD __fastcall LoadFromIn(TTransferInEvent OnTransferIn, TObject * Sender, DWORD Len);
   void __fastcall WriteToStream(TStream * Stream, const DWORD Len);
   void __fastcall WriteToOut(TTransferOutEvent OnTransferOut, TObject * Sender, const DWORD Len);
   __property TMemoryStream * Memory  = { read=FMemory, write=SetMemory };
@@ -40,6 +42,7 @@ private:
   void __fastcall SetSize(int value);
   void __fastcall SetPosition(int value);
   int __fastcall GetPosition() const;
+  void __fastcall ProcessRead(DWORD Len, DWORD Result);
 };
 //---------------------------------------------------------------------------
 class TSafeHandleStream : public THandleStream

+ 1 - 0
source/core/FtpFileSystem.cpp

@@ -1850,6 +1850,7 @@ bool __fastcall TFTPFileSystem::IsCapable(int Capability) const
     case fcResumeSupport:
     case fcChangePassword:
     case fcTransferOut:
+    case fcTransferIn:
       return false;
 
     default:

+ 1 - 0
source/core/S3FileSystem.cpp

@@ -672,6 +672,7 @@ bool __fastcall TS3FileSystem::IsCapable(int Capability) const
     case fcChangePassword:
     case fcLocking:
     case fcTransferOut:
+    case fcTransferIn:
       return false;
 
     default:

+ 1 - 0
source/core/ScpFileSystem.cpp

@@ -463,6 +463,7 @@ bool __fastcall TSCPFileSystem::IsCapable(int Capability) const
     case fcSkipTransfer:
     case fcParallelTransfers: // does not implement cpNoRecurse
     case fcTransferOut:
+    case fcTransferIn:
       return false;
 
     case fcChangePassword:

+ 27 - 3
source/core/Script.cpp

@@ -308,6 +308,7 @@ __fastcall TScript::TScript(bool LimitedOutput)
   FWantsProgress = false;
   FInteractive = false;
   FOnTransferOut = NULL;
+  FOnTransferIn = NULL;
   FIncludeFileMaskOptionUsed = false;
   FPendingLogLines = new TStringList();
 
@@ -1518,9 +1519,23 @@ void __fastcall TScript::PutProc(TScriptProcParams * Parameters)
 
   RequireParams(Parameters, 1);
   int LastFileParam = (Parameters->ParamCount == 1 ? 1 : Parameters->ParamCount - 1);
-  TStrings * FileList =
-    CreateLocalFileList(
-      Parameters, 1, LastFileParam, (TFileListType)(fltMask | FLAGMASK(Latest, fltLatest)));
+  DebugAssert(CopyParam.OnTransferIn == NULL);
+  TStrings * FileList;
+  // We use stdin only if - is the very first parameter
+  if ((OnTransferIn != NULL) && SameText(Parameters->Param[1], InOutParam))
+  {
+    if (Parameters->ParamCount > 2)
+    {
+      throw Exception(LoadStr(STREAM_IN_SCRIPT_ERROR));
+    }
+
+    CopyParam.OnTransferIn = OnTransferIn;
+    FileList = new TStringList();
+  }
+  else
+  {
+    FileList = CreateLocalFileList(Parameters, 1, LastFileParam, (TFileListType)(fltMask | FLAGMASK(Latest, fltLatest)));
+  }
   try
   {
     UnicodeString TargetDirectory;
@@ -1542,6 +1557,15 @@ void __fastcall TScript::PutProc(TScriptProcParams * Parameters)
       CheckMultiFilesToOne(FileList, Target, true);
     }
 
+    if (CopyParam.OnTransferIn != NULL)
+    {
+      if (IsFileNameMask(CopyParam.FileMask))
+      {
+        throw Exception(LoadStr(STREAM_IN_SCRIPT_ERROR));
+      }
+      FileList->Add(CopyParam.FileMask);
+    }
+
     CheckParams(Parameters);
 
     FTerminal->CopyToRemote(FileList, TargetDirectory, &CopyParam, Params, NULL);

+ 2 - 0
source/core/Script.h

@@ -77,6 +77,7 @@ public:
   __property bool WantsProgress = { read = FWantsProgress, write = FWantsProgress };
   __property bool Interactive = { read = FInteractive, write = FInteractive };
   __property TTransferOutEvent OnTransferOut = { read = FOnTransferOut, write = FOnTransferOut };
+  __property TTransferInEvent OnTransferIn = { read = FOnTransferIn, write = FOnTransferIn };
 
 protected:
   TTerminal * FTerminal;
@@ -105,6 +106,7 @@ protected:
   bool FWantsProgress;
   bool FInteractive;
   TTransferOutEvent FOnTransferOut;
+  TTransferInEvent FOnTransferIn;
   TStrings * FPendingLogLines;
   bool FWarnNonDefaultCopyParam;
   bool FWarnNonDefaultSynchronizeParams;

+ 1 - 1
source/core/SessionInfo.h

@@ -46,7 +46,7 @@ enum TFSCapability { fcUserGroupListing, fcModeChanging, fcGroupChanging,
   fcSecondaryShell, fcRemoveCtrlZUpload, fcRemoveBOMUpload, fcMoveToQueue,
   fcLocking, fcPreservingTimestampDirs, fcResumeSupport,
   fcChangePassword, fcSkipTransfer, fcParallelTransfers, fcBackgroundTransfers,
-  fcTransferOut,
+  fcTransferOut, fcTransferIn,
   fcCount };
 //---------------------------------------------------------------------------
 struct TFileSystemInfo

+ 39 - 18
source/core/SftpFileSystem.cpp

@@ -1498,6 +1498,7 @@ public:
     FEncryption(Encryption)
   {
     FStream = NULL;
+    FOnTransferIn = NULL;
     OperationProgress = NULL;
     FLastBlockSize = 0;
     FEnd = false;
@@ -1509,15 +1510,19 @@ public:
     delete FStream;
   }
 
-  bool __fastcall Init(const UnicodeString AFileName,
-    HANDLE AFile, TFileOperationProgressType * AOperationProgress,
+  bool __fastcall Init(const UnicodeString & AFileName,
+    HANDLE AFile, TTransferInEvent OnTransferIn, TFileOperationProgressType * AOperationProgress,
     const RawByteString AHandle, __int64 ATransferred,
     int ConvertParams)
   {
     FFileName = AFileName;
-    FStream = new TSafeHandleStream((THandle)AFile);
+    if (OnTransferIn == NULL)
+    {
+      FStream = new TSafeHandleStream((THandle)AFile);
+    }
     OperationProgress = AOperationProgress;
     FHandle = AHandle;
+    FOnTransferIn = OnTransferIn;
     FTransferred = ATransferred;
     FConvertParams = ConvertParams;
 
@@ -1541,11 +1546,21 @@ protected:
 
     if (Result)
     {
-      FILE_OPERATION_LOOP_BEGIN
+      bool Last;
+      if (FOnTransferIn != NULL)
+      {
+        size_t Read = BlockBuf.LoadFromIn(FOnTransferIn, FTerminal, BlockSize);
+        Last = (Read < BlockSize);
+      }
+      else
       {
-        BlockBuf.LoadStream(FStream, BlockSize, false);
+        FILE_OPERATION_LOOP_BEGIN
+        {
+          BlockBuf.LoadStream(FStream, BlockSize, false);
+        }
+        FILE_OPERATION_LOOP_END(FMTLOAD(READ_ERROR, (FFileName)));
+        Last = (FStream->Position >= FStream->Size);
       }
-      FILE_OPERATION_LOOP_END(FMTLOAD(READ_ERROR, (FFileName)));
 
       FEnd = (BlockBuf.Size == 0);
       Result = !FEnd;
@@ -1572,7 +1587,7 @@ protected:
 
         if (FEncryption != NULL)
         {
-          FEncryption->Encrypt(BlockBuf, (FStream->Position >= FStream->Size));
+          FEncryption->Encrypt(BlockBuf, Last);
         }
 
         Request->ChangeType(SSH_FXP_WRITE);
@@ -1641,6 +1656,7 @@ protected:
 
 private:
   TStream * FStream;
+  TTransferInEvent FOnTransferIn;
   TFileOperationProgressType * OperationProgress;
   UnicodeString FFileName;
   unsigned long FLastBlockSize;
@@ -2065,6 +2081,7 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
     case fcRemoveBOMUpload:
     case fcPreservingTimestampDirs:
     case fcTransferOut:
+    case fcTransferIn:
       return true;
 
     case fcMoveToQueue:
@@ -4485,7 +4502,8 @@ void __fastcall TSFTPFileSystem::Source(
     !OperationProgress->AsciiTransfer &&
     CopyParam->AllowResume(OperationProgress->LocalSize) &&
     IsCapable(fcRename) &&
-    !FTerminal->IsEncryptingFiles();
+    !FTerminal->IsEncryptingFiles() &&
+    (CopyParam->OnTransferIn == NULL);
 
   TOpenRemoteFileParams OpenParams;
   OpenParams.OverwriteMode = omOverwrite;
@@ -4611,7 +4629,7 @@ void __fastcall TSFTPFileSystem::Source(
   OpenParams.CopyParam = CopyParam;
   OpenParams.Params = Params;
   OpenParams.FileParams = &FileParams;
-  OpenParams.Confirmed = false;
+  OpenParams.Confirmed = (CopyParam->OnTransferIn != NULL);
   OpenParams.DontRecycle = false;
 
   FTerminal->LogEvent(0, L"Opening remote file.");
@@ -4635,19 +4653,22 @@ void __fastcall TSFTPFileSystem::Source(
   bool TransferFinished = false;
   __int64 DestWriteOffset = 0;
   TSFTPPacket CloseRequest;
-  bool SetRights = ((DoResume && DestFileExists) || CopyParam->PreserveRights);
-  bool SetProperties = (CopyParam->PreserveTime || SetRights);
+  bool PreserveRights = CopyParam->PreserveRights && (CopyParam->OnTransferIn == NULL);
+  bool PreserveExistingRights = DoResume && DestFileExists;
+  bool SetRights = (PreserveExistingRights || PreserveRights);
+  bool PreserveTime = CopyParam->PreserveTime && (CopyParam->OnTransferIn == NULL);
+  bool SetProperties = (PreserveTime || SetRights);
   TSFTPPacket PropertiesRequest(SSH_FXP_SETSTAT);
   TSFTPPacket PropertiesResponse;
   TRights Rights;
   if (SetProperties)
   {
     AddPathString(PropertiesRequest, DestFullName);
-    if (CopyParam->PreserveRights)
+    if (PreserveRights)
     {
       Rights = CopyParam->RemoteFileRights(Handle.Attrs);
     }
-    else if (DoResume && DestFileExists)
+    else if (PreserveExistingRights)
     {
       Rights = DestRights;
     }
@@ -4659,7 +4680,7 @@ void __fastcall TSFTPFileSystem::Source(
     unsigned short RightsNumber = Rights.NumberSet;
     PropertiesRequest.AddProperties(
       SetRights ? &RightsNumber : NULL, NULL, NULL,
-      CopyParam->PreserveTime ? &Handle.MTime : NULL,
+      PreserveTime ? &Handle.MTime : NULL,
       NULL, NULL, false, FVersion, FUtfStrings);
   }
 
@@ -4689,7 +4710,7 @@ void __fastcall TSFTPFileSystem::Source(
       int ConvertParams =
         FLAGMASK(CopyParam->RemoveCtrlZ, cpRemoveCtrlZ) |
         FLAGMASK(CopyParam->RemoveBOM, cpRemoveBOM);
-      Queue.Init(Handle.FileName, Handle.Handle, OperationProgress,
+      Queue.Init(Handle.FileName, Handle.Handle, CopyParam->OnTransferIn, OperationProgress,
         OpenParams.RemoteFileHandle,
         DestWriteOffset + OperationProgress->TransferredSize,
         ConvertParams);
@@ -4796,7 +4817,7 @@ void __fastcall TSFTPFileSystem::Source(
   if (SetProperties)
   {
     std::unique_ptr<TTouchSessionAction> TouchAction;
-    if (CopyParam->PreserveTime)
+    if (PreserveTime)
     {
       TDateTime MDateTime = UnixToDateTime(Handle.MTime, FTerminal->SessionData->DSTMode);
       FTerminal->LogEvent(FORMAT(L"Preserving timestamp [%s]",
@@ -4808,7 +4829,7 @@ void __fastcall TSFTPFileSystem::Source(
     // do record chmod only if it was explicitly requested,
     // not when it was implicitly performed to apply timestamp
     // of overwritten file to new file
-    if (CopyParam->PreserveRights)
+    if (PreserveRights)
     {
       ChmodAction.reset(new TChmodSessionAction(FTerminal->ActionLog, DestFullName, Rights));
     }
@@ -4841,7 +4862,7 @@ void __fastcall TSFTPFileSystem::Source(
         catch (...)
         {
           if (FTerminal->Active &&
-              (!CopyParam->PreserveRights && !CopyParam->PreserveTime))
+              (!PreserveRights && !PreserveTime))
           {
             DebugAssert(DoResume);
             FTerminal->LogEvent(L"Ignoring error preserving permissions of overwritten file");

+ 44 - 12
source/core/Terminal.cpp

@@ -5241,14 +5241,21 @@ bool __fastcall TTerminal::AllowLocalFileTransfer(
     TSearchRecSmart ASearchRec;
     if (SearchRec == NULL)
     {
-      FILE_OPERATION_LOOP_BEGIN
+      if (CopyParam->OnTransferIn != NULL)
+      {
+        ASearchRec.Clear();
+      }
+      else
       {
-        if (!FileSearchRec(FileName, ASearchRec))
+        FILE_OPERATION_LOOP_BEGIN
         {
-          RaiseLastOSError();
+          if (!FileSearchRec(FileName, ASearchRec))
+          {
+            RaiseLastOSError();
+          }
         }
+        FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
       }
-      FILE_OPERATION_LOOP_END(FMTLOAD(FILE_NOT_EXISTS, (FileName)));
       SearchRec = &ASearchRec;
     }
 
@@ -6727,6 +6734,11 @@ bool __fastcall TTerminal::CopyToRemote(
   bool Result = false;
   TOnceDoneOperation OnceDoneOperation = odoIdle;
 
+  if ((CopyParam->OnTransferIn != NULL) && !FFileSystem->IsCapable(fcTransferIn))
+  {
+    throw Exception(LoadStr(NOTSUPPORTED));
+  }
+
   try
   {
     __int64 Size;
@@ -6898,7 +6910,8 @@ void __fastcall TTerminal::SourceRobust(
 {
   TUploadSessionAction Action(ActionLog);
   bool * AFileTransferAny = FLAGSET(Flags, tfUseFileTransferAny) ? &FFileTransferAny : NULL;
-  TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny);
+  bool CanRetry = (CopyParam->OnTransferIn == NULL);
+  TRobustOperationLoop RobustLoop(this, OperationProgress, AFileTransferAny, CanRetry);
 
   do
   {
@@ -7107,7 +7120,12 @@ void __fastcall TTerminal::Source(
   const UnicodeString & TargetDir, const TCopyParamType * CopyParam, int Params,
   TFileOperationProgressType * OperationProgress, unsigned int Flags, TUploadSessionAction & Action, bool & ChildError)
 {
-  Action.FileName(ExpandUNCFileName(FileName));
+  UnicodeString ActionFileName = FileName;
+  if (CopyParam->OnTransferIn == NULL)
+  {
+    ActionFileName = ExpandUNCFileName(ActionFileName);
+  }
+  Action.FileName(ActionFileName);
 
   OperationProgress->SetFile(FileName, false);
 
@@ -7117,7 +7135,14 @@ void __fastcall TTerminal::Source(
   }
 
   TLocalFileHandle Handle;
-  OpenLocalFile(FileName, GENERIC_READ, Handle);
+  if (CopyParam->OnTransferIn == NULL)
+  {
+    OpenLocalFile(FileName, GENERIC_READ, Handle);
+  }
+  else
+  {
+    Handle.FileName = FileName;
+  }
 
   OperationProgress->SetFileInProgress();
 
@@ -7133,13 +7158,20 @@ void __fastcall TTerminal::Source(
   }
   else
   {
-    LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", (FileName)));
+    if (CopyParam->OnTransferIn != NULL)
+    {
+      LogEvent(FORMAT(L"Streaming \"%s\" to remote directory started.", (FileName)));
+    }
+    else
+    {
+      LogEvent(FORMAT(L"Copying \"%s\" to remote directory started.", (FileName)));
 
-    OperationProgress->SetLocalSize(Handle.Size);
+      OperationProgress->SetLocalSize(Handle.Size);
 
-    // Suppose same data size to transfer as to read
-    // (not true with ASCII transfer)
-    OperationProgress->SetTransferSize(OperationProgress->LocalSize);
+      // Suppose same data size to transfer as to read
+      // (not true with ASCII transfer)
+      OperationProgress->SetTransferSize(OperationProgress->LocalSize);
+    }
 
     if (IsCapable[fcTextMode])
     {

+ 1 - 0
source/core/WebDAVFileSystem.cpp

@@ -582,6 +582,7 @@ bool __fastcall TWebDAVFileSystem::IsCapable(int Capability) const
     case fcResumeSupport:
     case fcChangePassword:
     case fcTransferOut:
+    case fcTransferIn:
       return false;
 
     case fcLocking:

+ 2 - 0
source/resource/TextsCore.h

@@ -272,6 +272,8 @@
 #define INVALID_ENCRYPT_KEY     748
 #define UNREQUESTED_FILE        749
 #define STORE_NEW_HOSTKEY_ERROR 750
+#define STREAM_IN_SCRIPT_ERROR  751
+#define STREAM_READ_ERROR       752
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301

+ 2 - 0
source/resource/TextsCore1.rc

@@ -243,6 +243,8 @@ BEGIN
   INVALID_ENCRYPT_KEY, "**Invalid encryption key.**\n\nEncryption key for %s encryption must have %d bytes. It must be entered in hexadecimal representation (i.e. %d characters)."
   UNREQUESTED_FILE, "Server sent a file that was not requested."
   STORE_NEW_HOSTKEY_ERROR, "Failed to store new host key."
+  STREAM_IN_SCRIPT_ERROR, "When uploading streamed data, only one source can be specified and the target must specify a filename."
+  STREAM_READ_ERROR, "Error reading input stream."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"

+ 1 - 0
source/resource/TextsWin.h

@@ -301,6 +301,7 @@
 #define USAGE_PASSWORD          1590
 #define USAGE_INTERACTIVEINPUT  1591
 #define USAGE_STDOUT            1592
+#define USAGE_STDIN             1593
 
 #define WIN_FORMS_STRINGS       1600
 #define COPY_FILE               1605

+ 1 - 0
source/resource/TextsWin1.rc

@@ -306,6 +306,7 @@ BEGIN
         USAGE_PASSWORD, "An alternative way to provide a password"
         USAGE_INTERACTIVEINPUT, "All prompts are automatically cancelled"
         USAGE_STDOUT, "Allows streaming files to stdout (and redirects status output to stderr)"
+        USAGE_STDIN, "Allows streaming files from stdin"
 
         WIN_FORMS_STRINGS, "WIN_FORMS_STRINGS"
         COPY_FILE, "%s file '%s' to %s:"

+ 83 - 5
source/windows/ConsoleRunner.cpp

@@ -61,6 +61,7 @@ public:
   virtual void __fastcall WaitBeforeExit();
   virtual void __fastcall Progress(TScriptProgress & Progress);
   virtual void __fastcall TransferOut(const unsigned char * Data, size_t Len);
+  virtual size_t __fastcall TransferIn(unsigned char * Data, size_t Len);
   virtual UnicodeString __fastcall FinalLogMessage();
 
 protected:
@@ -473,6 +474,7 @@ bool __fastcall TOwnConsole::HasFlag(TConsoleFlag Flag) const
     case cfCommandLineOnly:
     case cfWantsProgress:
     case cfStdOut:
+    case cfStdIn:
       return false;
 
     default:
@@ -510,6 +512,12 @@ void __fastcall TOwnConsole::TransferOut(const unsigned char * DebugUsedArg(Data
   DebugFail();
 }
 //---------------------------------------------------------------------------
+size_t __fastcall TOwnConsole::TransferIn(unsigned char * DebugUsedArg(Data), size_t DebugUsedArg(Len))
+{
+  DebugFail();
+  return 0;
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall TOwnConsole::FinalLogMessage()
 {
   return UnicodeString();
@@ -518,7 +526,7 @@ UnicodeString __fastcall TOwnConsole::FinalLogMessage()
 class TExternalConsole : public TConsole
 {
 public:
-  __fastcall TExternalConsole(const UnicodeString Instance, bool NoInteractiveInput, bool StdOut);
+  __fastcall TExternalConsole(const UnicodeString Instance, bool NoInteractiveInput, bool StdOut, bool StdIn);
   virtual __fastcall ~TExternalConsole();
 
   virtual void __fastcall Print(UnicodeString Str, bool FromBeginning = false, bool Error = false);
@@ -532,6 +540,7 @@ public:
   virtual void __fastcall WaitBeforeExit();
   virtual void __fastcall Progress(TScriptProgress & Progress);
   virtual void __fastcall TransferOut(const unsigned char * Data, size_t Len);
+  virtual size_t __fastcall TransferIn(unsigned char * Data, size_t Len);
   virtual UnicodeString __fastcall FinalLogMessage();
 
 private:
@@ -545,6 +554,7 @@ private:
   bool FPipeOutput;
   bool FNoInteractiveInput;
   bool FStdOut;
+  bool FStdIn;
   bool FWantsProgress;
   bool FInteractive;
   unsigned int FMaxSend;
@@ -557,7 +567,7 @@ private:
 };
 //---------------------------------------------------------------------------
 __fastcall TExternalConsole::TExternalConsole(
-  const UnicodeString Instance, bool NoInteractiveInput, bool StdOut)
+  const UnicodeString Instance, bool NoInteractiveInput, bool StdOut, bool StdIn)
 {
   UnicodeString Name;
   Name = FORMAT(L"%s%s", (CONSOLE_EVENT_REQUEST, (Instance)));
@@ -601,6 +611,7 @@ __fastcall TExternalConsole::TExternalConsole(
 
   FNoInteractiveInput = NoInteractiveInput;
   FStdOut = StdOut;
+  FStdIn = StdIn;
   FMaxSend = 0;
 
   Init();
@@ -815,6 +826,7 @@ void __fastcall TExternalConsole::Init()
     CommStruct->Event = TConsoleCommStruct::INIT;
     CommStruct->InitEvent.WantsProgress = false;
     CommStruct->InitEvent.UseStdErr = FStdOut;
+    CommStruct->InitEvent.BinaryInput = FStdIn;
   }
   __finally
   {
@@ -866,6 +878,9 @@ bool __fastcall TExternalConsole::HasFlag(TConsoleFlag Flag) const
     case cfStdOut:
       return FStdOut;
 
+    case cfStdIn:
+      return FStdIn;
+
     default:
       DebugFail();
       return false;
@@ -965,6 +980,49 @@ void __fastcall TExternalConsole::TransferOut(const unsigned char * Data, size_t
   }
 }
 //---------------------------------------------------------------------------
+size_t __fastcall TExternalConsole::TransferIn(unsigned char * Data, size_t Len)
+{
+  size_t Offset = 0;
+  size_t Result = 0;
+  while ((Result == Offset) && (Offset < Len))
+  {
+    TConsoleCommStruct * CommStruct;
+    size_t BlockLen = std::min(Len - Offset, sizeof(CommStruct->TransferEvent.Data));
+
+    CommStruct = GetCommStruct();
+    try
+    {
+      CommStruct->Event = TConsoleCommStruct::TRANSFERIN;
+      CommStruct->TransferEvent.Len = BlockLen;
+      CommStruct->TransferEvent.Error = false;
+    }
+    __finally
+    {
+      FreeCommStruct(CommStruct);
+    }
+
+    SendEvent(INFINITE);
+
+    CommStruct = GetCommStruct();
+    try
+    {
+      if (CommStruct->TransferEvent.Error)
+      {
+        throw Exception(LoadStr(STREAM_READ_ERROR));
+      }
+      DebugAssert(CommStruct->TransferEvent.Len <= BlockLen);
+      Result += CommStruct->TransferEvent.Len;
+      memcpy(Data + Offset, CommStruct->TransferEvent.Data, CommStruct->TransferEvent.Len);
+      Offset += BlockLen;
+    }
+    __finally
+    {
+      FreeCommStruct(CommStruct);
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 class TNullConsole : public TConsole
 {
 public:
@@ -982,6 +1040,7 @@ public:
 
   virtual void __fastcall Progress(TScriptProgress & Progress);
   virtual void __fastcall TransferOut(const unsigned char * Data, size_t Len);
+  virtual size_t __fastcall TransferIn(unsigned char * Data, size_t Len);
   virtual UnicodeString __fastcall FinalLogMessage();
 };
 //---------------------------------------------------------------------------
@@ -1042,6 +1101,7 @@ bool __fastcall TNullConsole::HasFlag(TConsoleFlag Flag) const
     case cfCommandLineOnly:
     case cfWantsProgress:
     case cfStdOut:
+    case cfStdIn:
       return false;
 
     default:
@@ -1067,6 +1127,12 @@ void __fastcall TNullConsole::TransferOut(const unsigned char * DebugUsedArg(Dat
   DebugFail();
 }
 //---------------------------------------------------------------------------
+size_t __fastcall TNullConsole::TransferIn(unsigned char * DebugUsedArg(Data), size_t DebugUsedArg(Len))
+{
+  DebugFail();
+  return 0;
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall TNullConsole::FinalLogMessage()
 {
   return UnicodeString();
@@ -1141,6 +1207,7 @@ private:
   void __fastcall Failed(bool & AnyError);
   void __fastcall ScriptProgress(TScript * Script, TScriptProgress & Progress);
   void __fastcall ScriptTransferOut(TObject *, const unsigned char * Data, size_t Len);
+  size_t __fastcall ScriptTransferIn(TObject *, unsigned char * Data, size_t Len);
   void __fastcall ConfigurationChange(TObject * Sender);
 };
 //---------------------------------------------------------------------------
@@ -1782,6 +1849,11 @@ void __fastcall TConsoleRunner::ScriptTransferOut(TObject *, const unsigned char
   FConsole->TransferOut(Data, Len);
 }
 //---------------------------------------------------------------------------
+size_t __fastcall TConsoleRunner::ScriptTransferIn(TObject *, unsigned char * Data, size_t Len)
+{
+  return FConsole->TransferIn(Data, Len);
+}
+//---------------------------------------------------------------------------
 void __fastcall TConsoleRunner::SynchronizeControllerLog(
   TSynchronizeController * /*Controller*/, TSynchronizeLogEntry /*Entry*/,
   const UnicodeString Message)
@@ -2033,6 +2105,10 @@ int __fastcall TConsoleRunner::Run(const UnicodeString Session, TOptions * Optio
       {
         FScript->OnTransferOut = ScriptTransferOut;
       }
+      if (FConsole->HasFlag(cfStdIn))
+      {
+        FScript->OnTransferIn = ScriptTransferIn;
+      }
 
       UpdateTitle();
 
@@ -2225,7 +2301,7 @@ void __fastcall Usage(TConsole * Console)
     FORMAT(L"[/script=<file>] [/%s cmd1...] [/parameter // param1...]", (LowerCase(COMMAND_SWITCH))));
   if (CommandLineOnly)
   {
-    PrintUsageSyntax(Console, FORMAT(L"[/%s]", (LowerCase(STDOUT_SWITCH))));
+    PrintUsageSyntax(Console, FORMAT(L"[/%s] [/%s]", (LowerCase(STDOUT_SWITCH), LowerCase(STDIN_SWITCH))));
   }
   PrintUsageSyntax(Console,
     FORMAT(L"[/%s=<logfile> [/loglevel=<level>]] [/%s=[<count>%s]<size>]", (LowerCase(LOG_SWITCH), LowerCase(LOGSIZE_SWITCH), LOGSIZE_SEPARATOR)));
@@ -2285,6 +2361,7 @@ void __fastcall Usage(TConsole * Console)
   if (CommandLineOnly)
   {
     RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(STDOUT_SWITCH), USAGE_STDOUT);
+    RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(STDIN_SWITCH), USAGE_STDIN);
   }
   RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(LOG_SWITCH) + L"=", USAGE_LOG);
   RegisterSwitch(SwitchesUsage, L"/loglevel=", USAGE_LOGLEVEL);
@@ -2746,9 +2823,10 @@ int __fastcall Console(TConsoleMode Mode)
     if (Params->FindSwitch(L"consoleinstance", ConsoleInstance))
     {
       Configuration->Usage->Inc(L"ConsoleExternal");
-      bool NoInteractiveInput = Params->FindSwitch(NOINTERACTIVEINPUT_SWITCH);
       bool StdOut = Params->FindSwitch(STDOUT_SWITCH);
-      Console = new TExternalConsole(ConsoleInstance, NoInteractiveInput, StdOut);
+      bool StdIn = Params->FindSwitch(STDIN_SWITCH);
+      bool NoInteractiveInput = Params->FindSwitch(NOINTERACTIVEINPUT_SWITCH) || StdIn;
+      Console = new TExternalConsole(ConsoleInstance, NoInteractiveInput, StdOut, StdIn);
     }
     else if (Params->FindSwitch(L"Console") || (Mode != cmScripting))
     {

+ 4 - 1
source/windows/WinInterface.h

@@ -51,6 +51,7 @@ const int mpAllowContinueOnError = 0x02;
 #define BROWSE_SWITCH L"Browse"
 #define NOINTERACTIVEINPUT_SWITCH L"NoInteractiveInput"
 #define STDOUT_SWITCH L"StdOut"
+#define STDIN_SWITCH L"StdIn"
 
 #define DUMPCALLSTACK_EVENT L"WinSCPCallstack%d"
 
@@ -622,7 +623,8 @@ enum TConsoleFlag
   cfInteractive,
   cfCommandLineOnly,
   cfWantsProgress,
-  cfStdOut
+  cfStdOut,
+  cfStdIn
 };
 //---------------------------------------------------------------------------
 class TConsole
@@ -641,6 +643,7 @@ public:
   virtual void __fastcall WaitBeforeExit() = 0;
   virtual void __fastcall Progress(TScriptProgress & Progress) = 0;
   virtual void __fastcall TransferOut(const unsigned char * Data, size_t Len) = 0;
+  virtual size_t __fastcall TransferIn(unsigned char * Data, size_t Len) = 0;
   virtual UnicodeString __fastcall FinalLogMessage() = 0;
 };
 //---------------------------------------------------------------------------