/// <summary> /// Retrieves a remote file from the FTP server and writes the data to a local file /// specfied in the localPath parameter. /// </summary> /// <remarks> /// Note that some FTP servers will not accept a full path. On those systems you must navigate to /// the directory you wish to get the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() /// method. /// </remarks> /// <param name="remotePath">A path and/or file name to the remote file.</param> /// <param name="localPath">A fully qualified local path to a file on the local machine.</param> /// <param name="action">The type of action to take.</param> /// <seealso cref="GetFileAsync(string, string, FileAction)"/> /// <seealso cref="PutFile(string, string, FileAction)"/> /// <seealso cref="PutFileAsync(string, string, FileAction)"/> /// <seealso cref="MoveFile"/> /// <seealso cref="FxpCopy"/> /// <seealso cref="FxpCopyAsync"/> public void GetFile(string remotePath, string localPath, FileAction action) { if (remotePath == null) throw new ArgumentNullException("remotePath"); if (remotePath.Length == 0) throw new ArgumentException("must contain a value", "remotePath"); if (localPath == null) throw new ArgumentNullException("localPath"); if (localPath.Length == 0) throw new ArgumentException("must contain a value", "localPath"); if (action == FileAction.None) throw new ArgumentOutOfRangeException("action", "must contain a value other than 'Unknown'"); localPath = CorrectLocalPath(localPath); WriteToLog(String.Format("Action='GetFile';Status='TransferBegin';LocalPath='{0}';RemotePath='{1}';FileAction='{1}'", localPath, remotePath, action.ToString())); FtpRequest request = new FtpRequest(base.CharacterEncoding, FtpCmd.Retr, remotePath); try { switch (action) { case FileAction.CreateNew: // create a file stream to stream the file locally to disk that only creates the file if it does not already exist using (Stream localFile = File.Open(localPath, FileMode.CreateNew)) { base.TransferData(TransferDirection.ToClient, request, localFile); } break; case FileAction.Create: // create a file stream to stream the file locally to disk using (Stream localFile = File.Open(localPath, FileMode.Create)) { base.TransferData(TransferDirection.ToClient, request, localFile); } break; case FileAction.CreateOrAppend: // open the local file using (Stream localFile = File.Open(localPath, FileMode.OpenOrCreate)) { // set the file position to the end so that any new data will be appended localFile.Position = localFile.Length; base.TransferData(TransferDirection.ToClient, request, localFile); } break; case FileAction.Resume: using (Stream localFile = File.Open(localPath, FileMode.Open)) { // get the size of the file already on the server (in bytes) long remoteSize = GetFileSize(remotePath); // if the files are the same size then there is nothing to transfer if (localFile.Length == remoteSize) return; base.TransferData(TransferDirection.ToClient, request, localFile, localFile.Length - 1); } break; case FileAction.ResumeOrCreate: if (File.Exists(localPath) && (new FileInfo(localPath)).Length > 0) GetFile(remotePath, localPath, FileAction.Resume); else GetFile(remotePath, localPath, FileAction.Create); break; } } catch (Exception ex) { WriteToLog(String.Format("Action='GetFile';Status='TransferError';LocalPath='{0}';RemotePath='{1}';FileAction='{1}';ErrorMessage='{2}", localPath, remotePath, action.ToString(), ex.Message)); throw new FtpException(String.Format("An unexpected exception occurred while retrieving file '{0}'.", remotePath), base.LastResponse, ex); } WriteToLog(String.Format("Action='GetFile';Status='TransferSuccess';LocalPath='{0}';RemotePath='{1}';FileAction='{1}'", localPath, remotePath, action.ToString())); }
/// <summary> /// Retrieves a remote file from the FTP server and writes the data to a local stream object /// specfied in the outStream parameter. /// </summary> /// <param name="remotePath">A path and/or file name to the remote file.</param> /// <param name="outStream">An output stream object used to stream the remote file to the local machine.</param> /// <param name="restart">A true/false value to indicate if the file download needs to be restarted due to a previous partial download.</param> /// <remarks> /// If the remote path is a file name then the file is downloaded from the FTP server current working directory. Otherwise a fully qualified /// path for the remote file may be specified. The output stream must be writeable and can be any stream object. Finally, the restart parameter /// is used to send a restart command to the FTP server. The FTP server is instructed to restart the download process at the last position of /// of the output stream. Not all FTP servers support the restart command. If the FTP server does not support the restart (REST) command, /// an FtpException error is thrown. /// Note that some FTP servers will not accept a full path. On those systems you must navigate to /// the directory you wish to get the file using with the ChangeDirectory() or ChangeDirectoryMultiPath() /// method. /// </remarks> /// <seealso cref="GetFileAsync(string, string, FileAction)"/> /// <seealso cref="PutFile(string, string, FileAction)"/> /// <seealso cref="PutFileAsync(string, string, FileAction)"/> /// <seealso cref="MoveFile"/> /// <seealso cref="FxpCopy"/> /// <seealso cref="FxpCopyAsync"/> public void GetFile(string remotePath, Stream outStream, bool restart) { if (remotePath == null) throw new ArgumentNullException("remotePath"); if (remotePath.Length == 0) throw new ArgumentException("must contain a value", "remotePath"); if (outStream == null) throw new ArgumentNullException("outStream"); if (outStream.CanWrite == false) throw new ArgumentException("must be writable. The CanWrite property must return the value 'true'.", "outStream"); FtpRequest request = new FtpRequest(base.CharacterEncoding, FtpCmd.Retr, remotePath); if (restart) { // get the size of the file already on the server (in bytes) long remoteSize = GetFileSize(remotePath); // if the files are the same size then there is nothing to transfer if (outStream.Length == remoteSize) return; base.TransferData(TransferDirection.ToClient, request, outStream, outStream.Length - 1); } else { base.TransferData(TransferDirection.ToClient, request, outStream); } }
internal string TransferText(FtpRequest request) { Stream output = new MemoryStream(); TransferData(TransferDirection.ToClient, request, output); output.Position = 0; StreamReader reader = new StreamReader(output, _encoding); return reader.ReadToEnd(); }
private void DoIntegrityCheck(FtpRequest request, Stream stream, long restartPosition) { // get the file path from the request argument string path = request.Arguments[0]; long startPos = restartPosition; long endPos = stream.Length; string streamHash = ComputeChecksum(_hashAlgorithm, stream, startPos); string serverHash = GetChecksum(_hashAlgorithm, path, startPos, endPos); // string compare the dataHash to the server hash value and see if they are the same if (String.Compare(streamHash, serverHash, StringComparison.InvariantCultureIgnoreCase) != 0) throw new FtpFileIntegrityException(String.Format("File integrity check failed. The destination integrity value '{0}' for the file '{1}' did not match the data transfer integrity value '{2}'.", serverHash, path, streamHash)); }
internal void TransferData(TransferDirection direction, FtpRequest request, Stream data, long restartPosition) { if (_commandConn == null || _commandConn.Connected == false) throw new FtpConnectionClosedException("Connection is closed."); if (request == null) throw new ArgumentNullException("request", "value is required"); if (data == null) throw new ArgumentNullException("data", "value is required"); switch (direction) { case TransferDirection.ToClient: if (!data.CanWrite) throw new FtpDataTransferException("Data transfer error. Data conn does not allow write operation."); break; case TransferDirection.ToServer: if (!data.CanRead) throw new FtpDataTransferException("Data transfer error. Data conn does not allow read operation."); break; } // if this is a restart then the data stream must support seeking if (restartPosition > 0 && !data.CanSeek) throw new FtpDataTransferException("Data transfer restart error. Data conn does not allow seek operation."); try { // create a thread to begin the process of opening a data connection to the remote server OpenDataConn(); // check for a restart position if (restartPosition > 0) { // instruct the server to restart file transfer at the same position where the output stream left off SendRequest(new FtpRequest(_encoding, FtpCmd.Rest, restartPosition.ToString(CultureInfo.InvariantCulture))); // set the data stream to the same position as the server data.Position = restartPosition; } // send the data transfer command that requires a separate data connection to be established to transmit data SendRequest(request); // wait for the data connection thread to signal back that a connection has been established WaitForDataConn(); // test to see if the data connection failed to be established sometime the active connection fails due to security settings on the ftp server if (_dataConn == null) throw new FtpDataConnectionException("Unable to establish a data connection to the destination. The destination may have refused the connection."); // create the data stream object - handles creation of SslStream and DeflateStream objects as well Stream conn = _dataConn.GetStream(); // test to see if we need to enable ssl/tls explicit mode if (_securityProtocol != FtpSecurityProtocol.None) { conn = CreateSslStream(conn); } // test to see if we need to enable compression by using the DeflateStream if (_isCompressionEnabled) { conn = CreateZlibStream(direction, conn); } // based on the direction of the data transfer we need to handle the input and output streams switch (direction) { case TransferDirection.ToClient: TransferBytes(conn, data, _maxDownloadSpeed * 1024); break; case TransferDirection.ToServer: TransferBytes(data, conn, _maxUploadSpeed * 1024); break; } } finally { // attempt to close the data connection CloseDataConn(); } // if no errors occurred and this is not a quoted command then we will wait for the server to send a closing connection message WaitForHappyCodes(FtpResponseCode.ClosingDataConnection); // integrity check if (_hashAlgorithm != HashingFunction.None && request.IsFileTransfer) DoIntegrityCheck(request, data, restartPosition); }
internal void TransferData(TransferDirection direction, FtpRequest request, Stream data) { TransferData(direction, request, data, 0); }
/// <summary> /// Send a FTP command request to the server. /// </summary> /// <param name="request"></param> internal void SendRequest(FtpRequest request) { if (_commandConn == null || _commandConn.Connected == false) throw new FtpConnectionClosedException("Connection is closed."); // clear out any responses that might have been pending from a previous // failed operation DontWaitForHappyCodes(); if (ClientRequest != null) ClientRequest(this, new FtpRequestEventArgs(request)); byte[] buffer = request.GetBytes(); try { _commandStream.Write(buffer, 0, buffer.Length); } catch (IOException ex) { throw new FtpConnectionBrokenException("Connection is broken. Failed to send command.", ex); } // most commands will have happy codes but the quote() command does not if (request.HasHappyCodes) { WaitForHappyCodes(request.GetHappyCodes()); } else { // when there are no happy codes given the we have to give the server some time to response // since we really don't know what response is the correct one if (request.Command != FtpCmd.Quit) Thread.Sleep(2000); DontWaitForHappyCodes(); } }
/// <summary> /// Constructor for FtpRequestEventArgs. /// </summary> /// <param name="request">An FtpRequest object.</param> public FtpRequestEventArgs(FtpRequest request) { _request = request; }