/// <summary> /// Returns a file/directory listing using the NLST command. /// </summary> /// <param name="path">The path of the directory to list</param> /// <returns>A string array of file and directory names if any were returned.</returns> /// <example><code source="..\Examples\GetNameListing.cs" lang="cs" /></example> public string[] GetNameListing(string path) { this.LogFunc("GetNameListing", new object[] { path }); List <string> listing = new List <string>(); // calc path to request path = GetAbsolutePath(path); #if !CORE14 lock (m_lock) { #endif // always get the file listing in binary // to avoid any potential character translation // problems that would happen if in ASCII. Execute("TYPE I"); using (FtpDataStream stream = OpenDataStream(("NLST " + path.GetFtpPath()), 0)) { string buf; try { while ((buf = stream.ReadLine(Encoding)) != null) { listing.Add(buf); } } finally { stream.Close(); } } #if !CORE14 } #endif return(listing.ToArray()); }
/// <summary> /// Returns a file/directory listing using the NLST command asynchronously /// </summary> /// <param name="path">The path of the directory to list</param> /// <param name="token">Cancellation Token</param> /// <returns>An array of file and directory names if any were returned.</returns> public async Task <string[]> GetNameListingAsync(string path, CancellationToken token = default(CancellationToken)) { this.LogFunc(nameof(GetNameListingAsync), new object[] { path }); List <string> listing = new List <string>(); // calc path to request path = await GetAbsolutePathAsync(path, token); // always get the file listing in binary // to avoid any potential character translation // problems that would happen if in ASCII. await ExecuteAsync("TYPE I", token); using (FtpDataStream stream = await OpenDataStreamAsync(("NLST " + path.GetFtpPath()), 0, token)) { string buf; try { while ((buf = await stream.ReadLineAsync(Encoding, token)) != null) { listing.Add(buf); } } finally { stream.Close(); } } return(listing.ToArray()); }
/// <summary> /// Opens the specified file for appending. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// </summary> /// <param name="path">The full or relative path to the file to be opened</param> /// <param name="type">ASCII/Binary</param> /// <returns>A stream for writing to the file on the server</returns> /// <example><code source="..\Examples\OpenAppend.cs" lang="cs" /></example> public virtual Stream OpenAppend(string path, FtpDataType type) { FtpTrace.WriteFunc("OpenAppend", new object[] { path, type }); FtpClient client = null; FtpDataStream stream = null; long length = 0; lock (m_lock) { if (m_threadSafeDataChannels) { client = CloneConnection(); client.Connect(); client.SetWorkingDirectory(GetWorkingDirectory()); } else { client = this; } client.SetDataType(type); length = client.GetFileSize(path); stream = client.OpenDataStream(("APPE " + path.GetFtpPath()), 0); if (length > 0 && stream != null) { stream.SetLength(length); stream.SetPosition(length); } } return(stream); }
/// <summary> /// Opens a data stream. /// </summary> /// <param name='command'>The command to execute that requires a data stream</param> /// <param name="restart">Restart location in bytes for file transfer</param> /// <returns>The data stream.</returns> FtpDataStream OpenDataStream(string command, long restart) { FtpDataConnectionType type = m_dataConnectionType; FtpDataStream stream = null; #if !CORE14 lock (m_lock) { #endif if (!IsConnected) { Connect(); } // The PORT and PASV commands do not work with IPv6 so // if either one of those types are set change them // to EPSV or EPRT appropriately. if (m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) { switch (type) { case FtpDataConnectionType.PORT: type = FtpDataConnectionType.EPRT; FtpTrace.WriteLine(FtpTraceLevel.Info, "Changed data connection type to EPRT because we are connected with IPv6."); break; case FtpDataConnectionType.PASV: case FtpDataConnectionType.PASVEX: type = FtpDataConnectionType.EPSV; FtpTrace.WriteLine(FtpTraceLevel.Info, "Changed data connection type to EPSV because we are connected with IPv6."); break; } } switch (type) { case FtpDataConnectionType.AutoPassive: case FtpDataConnectionType.EPSV: case FtpDataConnectionType.PASV: case FtpDataConnectionType.PASVEX: stream = OpenPassiveDataStream(type, command, restart); break; case FtpDataConnectionType.AutoActive: case FtpDataConnectionType.EPRT: case FtpDataConnectionType.PORT: stream = OpenActiveDataStream(type, command, restart); break; } if (stream == null) { throw new InvalidOperationException("The specified data channel type is not implemented."); } #if !CORE14 } #endif return(stream); }
/// <summary> /// Opens the specified file for reading /// </summary> /// <param name="path">The full or relative path of the file</param> /// <param name="type">ASCII/Binary</param> /// <param name="restart">Resume location</param> /// <returns>A stream for reading the file on the server</returns> /// <example><code source="..\Examples\OpenRead.cs" lang="cs" /></example> public virtual Stream OpenRead(string path, FtpDataType type, long restart) { // verify args if (path.IsBlank()) { throw new ArgumentException("Required parameter is null or blank.", "path"); } FtpTrace.WriteFunc("OpenRead", new object[] { path, type, restart }); FtpClient client = null; FtpDataStream stream = null; long length = 0; #if !CORE14 lock (m_lock) { #endif if (m_threadSafeDataChannels) { client = CloneConnection(); client.Connect(); client.SetWorkingDirectory(GetWorkingDirectory()); } else { client = this; } client.SetDataType(type); length = client.GetFileSize(path); stream = client.OpenDataStream(("RETR " + path.GetFtpPath()), restart); #if !CORE14 } #endif if (stream != null) { if (length > 0) { stream.SetLength(length); } if (restart > 0) { stream.SetPosition(restart); } } return(stream); }
/// <summary> /// Disconnects a data stream /// </summary> /// <param name="stream">The data stream to close</param> internal FtpReply CloseDataStream(FtpDataStream stream) { FtpTrace.WriteFunc("CloseDataStream"); FtpReply reply = new FtpReply(); if (stream == null) { throw new ArgumentException("The data stream parameter was null"); } #if !CORE14 lock (m_lock) { #endif try { if (IsConnected) { // if the command that required the data connection was // not successful then there will be no reply from // the server, however if the command was successful // the server will send a reply when the data connection // is closed. if (stream.CommandStatus.Type == FtpResponseType.PositivePreliminary) { if (!(reply = GetReply()).Success) { throw new FtpCommandException(reply); } } } } finally { // if this is a clone of the original control // connection we should Dispose() if (IsClone) { Disconnect(); Dispose(); } } #if !CORE14 } #endif return(reply); }
/// <summary> /// Returns a file/directory listing using the NLST command. /// </summary> /// <param name="path">The path of the directory to list</param> /// <returns>A string array of file and directory names if any were returned.</returns> /// <example><code source="..\Examples\GetNameListing.cs" lang="cs" /></example> public string[] GetNameListing(string path) { this.LogFunc("GetNameListing", new object[] { path }); List <string> listing = new List <string>(); // calc path to request path = GetAbsolutePath(path); #if !CORE14 lock (m_lock) { #endif // always get the file listing in binary // to avoid any potential character translation // problems that would happen if in ASCII. Execute("TYPE I"); // read in raw listing try { using (FtpDataStream stream = OpenDataStream(("NLST " + path.GetFtpPath()), 0)) { string buf; try { while ((buf = stream.ReadLine(Encoding)) != null) { listing.Add(buf); } } finally { stream.Close(); } } } catch (FtpMissingSocketException) { // Some FTP server does not send any response when listing an empty directory // and the connection fails because no communication socket is provided by the server } catch (IOException) { // Some FTP servers forcibly close the connection, we absorb these errors } #if !CORE14 } #endif return(listing.ToArray()); }
/// <summary> /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// </summary> /// <param name="path">Full or relative path of the file</param> /// <param name="type">ASCII/Binary</param> /// <param name="checkIfFileExists">Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length.</param> /// <returns>A stream for writing to the file on the server</returns> /// <example><code source="..\Examples\OpenWrite.cs" lang="cs" /></example> public virtual Stream OpenWrite(string path, FtpDataType type, bool checkIfFileExists) { // verify args if (path.IsBlank()) { throw new ArgumentException("Required parameter is null or blank.", "path"); } FtpTrace.WriteFunc("OpenWrite", new object[] { path, type }); FtpClient client = null; FtpDataStream stream = null; long length = 0; #if !CORE14 lock (m_lock) { #endif if (m_threadSafeDataChannels) { client = CloneConnection(); client.Connect(); client.SetWorkingDirectory(GetWorkingDirectory()); } else { client = this; } client.SetDataType(type); length = checkIfFileExists ? client.GetFileSize(path) : 0; stream = client.OpenDataStream(("STOR " + path.GetFtpPath()), 0); if (length > 0 && stream != null) { stream.SetLength(length); } #if !CORE14 } #endif return(stream); }
/// <summary> /// Returns a file/directory listing using the NLST command asynchronously /// </summary> /// <param name="path">The path of the directory to list</param> /// <param name="token">Cancellation Token</param> /// <returns>An array of file and directory names if any were returned.</returns> public async Task <string[]> GetNameListingAsync(string path, CancellationToken token = default(CancellationToken)) { this.LogFunc(nameof(GetNameListingAsync), new object[] { path }); List <string> listing = new List <string>(); // calc path to request path = await GetAbsolutePathAsync(path, token); // always get the file listing in binary // to avoid any potential character translation // problems that would happen if in ASCII. await ExecuteAsync("TYPE I", token); // read in raw listing try { using (FtpDataStream stream = await OpenDataStreamAsync(("NLST " + path.GetFtpPath()), 0, token)) { string buf; try { while ((buf = await stream.ReadLineAsync(Encoding, token)) != null) { listing.Add(buf); } } finally { stream.Close(); } } } catch (FtpMissingSocketException) { // Some FTP server does not send any response when listing an empty directory // and the connection fails because no communication socket is provided by the server } catch (IOException) { // Some FTP servers forcibly close the connection, we absorb these errors } return(listing.ToArray()); }
/// <summary> /// Gets a file listing from the server asynchronously. Each <see cref="FtpListItem"/> object returned /// contains information about the file that was able to be retrieved. /// </summary> /// <remarks> /// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the /// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// </remarks> /// <param name="path">The path to list</param> /// <param name="options">Options that dictate how the list operation is performed</param> /// <param name="token">Cancellation Token</param> /// <returns>An array of items retrieved in the listing</returns> public async Task <FtpListItem[]> GetListingAsync(string path, FtpListOption options, CancellationToken token = default(CancellationToken)) { // start recursive process if needed and unsupported by the server if ((options & FtpListOption.Recursive) == FtpListOption.Recursive && !RecursiveList) { return(await GetListingRecursiveAsync(GetAbsolutePath(path), options)); } this.LogFunc(nameof(GetListingAsync), new object[] { path, options }); FtpListItem item = null; List <FtpListItem> lst = new List <FtpListItem>(); List <string> rawlisting = new List <string>(); string listcmd = null; string buf = null; // read flags bool isIncludeSelf = (options & FtpListOption.IncludeSelfAndParent) == FtpListOption.IncludeSelfAndParent; bool isForceList = (options & FtpListOption.ForceList) == FtpListOption.ForceList; bool isNoPath = (options & FtpListOption.NoPath) == FtpListOption.NoPath; bool isNameList = (options & FtpListOption.NameList) == FtpListOption.NameList; bool isUseLS = (options & FtpListOption.UseLS) == FtpListOption.UseLS; bool isAllFiles = (options & FtpListOption.AllFiles) == FtpListOption.AllFiles; bool isRecursive = (options & FtpListOption.Recursive) == FtpListOption.Recursive && RecursiveList; bool isDerefLinks = (options & FtpListOption.DerefLinks) == FtpListOption.DerefLinks; bool isGetModified = (options & FtpListOption.Modify) == FtpListOption.Modify; bool isGetSize = (options & FtpListOption.Size) == FtpListOption.Size; // calc path to request path = await GetAbsolutePathAsync(path, token); // MLSD provides a machine readable format with 100% accurate information // so always prefer MLSD over LIST unless the caller of this method overrides it with the ForceList option bool machineList = false; if ((!isForceList || m_parser == FtpParser.Machine) && HasFeature(FtpCapability.MLSD)) { listcmd = "MLSD"; machineList = true; } else { if (isUseLS) { listcmd = "LS"; } else if (isNameList) { listcmd = "NLST"; } else { string listopts = ""; listcmd = "LIST"; if (isAllFiles) { listopts += "a"; } if (isRecursive) { listopts += "R"; } if (listopts.Length > 0) { listcmd += " -" + listopts; } } } if (!isNoPath) { listcmd = (listcmd + " " + path.GetFtpPath()); } await ExecuteAsync("TYPE I", token); // read in raw file listing try { using (FtpDataStream stream = await OpenDataStreamAsync(listcmd, 0, token)) { try { this.LogLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); if (this.BulkListing) { // increases performance of GetListing by reading multiple lines of the file listing at once foreach (var line in await stream.ReadAllLinesAsync(Encoding, this.BulkListingLength, token)) { if (!FtpExtensions.IsNullOrWhiteSpace(line)) { rawlisting.Add(line); this.LogLine(FtpTraceLevel.Verbose, "Listing: " + line); } } } else { // GetListing will read file listings line-by-line (actually byte-by-byte) while ((buf = await stream.ReadLineAsync(Encoding, token)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); this.LogLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } } this.LogLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } } catch (FtpMissingSocketException) { // Some FTP server does not send any response when listing an empty directory // and the authentication fails, if no communication socket is provided by the server } for (int i = 0; i < rawlisting.Count; i++) { buf = rawlisting[i]; if (isNameList) { // if NLST was used we only have a file name so // there is nothing to parse. item = new FtpListItem() { FullName = buf }; if (await DirectoryExistsAsync(item.FullName, token)) { item.Type = FtpFileSystemObjectType.Directory; } else { item.Type = FtpFileSystemObjectType.File; } lst.Add(item); } else { // if this is a result of LIST -R then the path will be spit out // before each block of objects if (listcmd.StartsWith("LIST") && isRecursive) { if (buf.StartsWith("/") && buf.EndsWith(":")) { path = buf.TrimEnd(':'); continue; } } // if the next line in the listing starts with spaces // it is assumed to be a continuation of the current line if (i + 1 < rawlisting.Count && (rawlisting[i + 1].StartsWith("\t") || rawlisting[i + 1].StartsWith(" "))) { buf += rawlisting[++i]; } try { item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList); } catch (FtpListParser.CriticalListParseException) { this.LogStatus(FtpTraceLevel.Verbose, "Restarting parsing from first entry in list"); i = -1; lst.Clear(); continue; } // FtpListItem.Parse() returns null if the line // could not be parsed if (item != null) { if (isIncludeSelf || !(item.Name == "." || item.Name == "..")) { lst.Add(item); } else { //this.LogStatus(FtpTraceLevel.Verbose, "Skipped self or parent item: " + item.Name); } } else { this.LogStatus(FtpTraceLevel.Warn, "Failed to parse file listing: " + buf); } } // load extended information that wasn't available if the list options flags say to do so. if (item != null) { // try to dereference symbolic links if the appropriate list // option was passed if (item.Type == FtpFileSystemObjectType.Link && isDerefLinks) { item.LinkObject = await DereferenceLinkAsync(item, token); } // if need to get file modified date if (isGetModified && HasFeature(FtpCapability.MDTM)) { // if the modified date was not loaded or the modified date is more than a day in the future // and the server supports the MDTM command, load the modified date. // most servers do not support retrieving the modified date // of a directory but we try any way. if (item.Modified == DateTime.MinValue || listcmd.StartsWith("LIST")) { DateTime modify; if (item.Type == FtpFileSystemObjectType.Directory) { this.LogStatus(FtpTraceLevel.Verbose, "Trying to retrieve modification time of a directory, some servers don't like this..."); } if ((modify = await GetModifiedTimeAsync(item.FullName, token: token)) != DateTime.MinValue) { item.Modified = modify; } } } // if need to get file size if (isGetSize && HasFeature(FtpCapability.SIZE)) { // if no size was parsed, the object is a file and the server // supports the SIZE command, then load the file size if (item.Size == -1) { if (item.Type != FtpFileSystemObjectType.Directory) { item.Size = await GetFileSizeAsync(item.FullName, token); } else { item.Size = 0; } } } } } return(lst.ToArray()); }
/// <summary> /// Gets a file listing from the server. Each <see cref="FtpListItem"/> object returned /// contains information about the file that was able to be retrieved. /// </summary> /// <remarks> /// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the /// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// </remarks> /// <param name="path">The path of the directory to list</param> /// <param name="options">Options that dictacte how a list is performed and what information is gathered.</param> /// <returns>An array of FtpListItem objects</returns> /// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example> public FtpListItem[] GetListing(string path, FtpListOption options) { FtpTrace.WriteFunc("GetListing", new object[] { path, options }); FtpListItem item = null; List <FtpListItem> lst = new List <FtpListItem>(); List <string> rawlisting = new List <string>(); string listcmd = null; string buf = null; // read flags bool isIncludeSelf = (options & FtpListOption.IncludeSelfAndParent) == FtpListOption.IncludeSelfAndParent; bool isForceList = (options & FtpListOption.ForceList) == FtpListOption.ForceList; bool isNoPath = (options & FtpListOption.NoPath) == FtpListOption.NoPath; bool isNameList = (options & FtpListOption.NameList) == FtpListOption.NameList; bool isUseLS = (options & FtpListOption.UseLS) == FtpListOption.UseLS; bool isAllFiles = (options & FtpListOption.AllFiles) == FtpListOption.AllFiles; bool isRecursive = (options & FtpListOption.Recursive) == FtpListOption.Recursive && RecursiveList; bool isDerefLinks = (options & FtpListOption.DerefLinks) == FtpListOption.DerefLinks; bool isGetModified = (options & FtpListOption.Modify) == FtpListOption.Modify; bool isGetSize = (options & FtpListOption.Size) == FtpListOption.Size; // calc path to request path = GetAbsolutePath(path); // MLSD provides a machine readable format with 100% accurate information // so always prefer MLSD over LIST unless the caller of this method overrides it with the ForceList option bool machineList = false; if ((!isForceList || m_parser == FtpParser.Machine) && HasFeature(FtpCapability.MLSD)) { listcmd = "MLSD"; machineList = true; } else { if (isUseLS) { listcmd = "LS"; } else if (isNameList) { listcmd = "NLST"; } else { string listopts = ""; listcmd = "LIST"; if (isAllFiles) { listopts += "a"; } if (isRecursive) { listopts += "R"; } if (listopts.Length > 0) { listcmd += " -" + listopts; } } } if (!isNoPath) { listcmd = (listcmd + " " + path.GetFtpPath()); } #if !CORE14 lock (m_lock) { #endif Execute("TYPE I"); // read in raw file listing using (FtpDataStream stream = OpenDataStream(listcmd, 0)) { try { FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); while ((buf = stream.ReadLine(Encoding)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } #if !CORE14 } #endif for (int i = 0; i < rawlisting.Count; i++) { buf = rawlisting[i]; if (isNameList) { // if NLST was used we only have a file name so // there is nothing to parse. item = new FtpListItem() { FullName = buf }; if (DirectoryExists(item.FullName)) { item.Type = FtpFileSystemObjectType.Directory; } else { item.Type = FtpFileSystemObjectType.File; } lst.Add(item); } else { // if this is a result of LIST -R then the path will be spit out // before each block of objects if (listcmd.StartsWith("LIST") && isRecursive) { if (buf.StartsWith("/") && buf.EndsWith(":")) { path = buf.TrimEnd(':'); continue; } } // if the next line in the listing starts with spaces // it is assumed to be a continuation of the current line if (i + 1 < rawlisting.Count && (rawlisting[i + 1].StartsWith("\t") || rawlisting[i + 1].StartsWith(" "))) { buf += rawlisting[++i]; } item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList); // FtpListItem.Parse() returns null if the line // could not be parsed if (item != null) { if (isIncludeSelf || !(item.Name == "." || item.Name == "..")) { lst.Add(item); } else { //FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped self or parent item: " + item.Name); } } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse file listing: " + buf); } } // load extended information that wasn't available if the list options flags say to do so. if (item != null) { // try to dereference symbolic links if the appropriate list // option was passed if (item.Type == FtpFileSystemObjectType.Link && isDerefLinks) { item.LinkObject = DereferenceLink(item); } // if need to get file modified date if (isGetModified && HasFeature(FtpCapability.MDTM)) { // if the modified date was not loaded or the modified date is more than a day in the future // and the server supports the MDTM command, load the modified date. // most servers do not support retrieving the modified date // of a directory but we try any way. if (item.Modified == DateTime.MinValue || listcmd.StartsWith("LIST")) { DateTime modify; if (item.Type == FtpFileSystemObjectType.Directory) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Trying to retrieve modification time of a directory, some servers don't like this..."); } if ((modify = GetModifiedTime(item.FullName)) != DateTime.MinValue) { item.Modified = modify; } } } // if need to get file size if (isGetSize && HasFeature(FtpCapability.SIZE)) { // if no size was parsed, the object is a file and the server // supports the SIZE command, then load the file size if (item.Size == -1) { if (item.Type != FtpFileSystemObjectType.Directory) { item.Size = GetFileSize(item.FullName); } else { item.Size = 0; } } } } } return(lst.ToArray()); }
/// <summary> /// Opens the specified type of passive data stream /// </summary> /// <param name="type">Type of passive data stream to open</param> /// <param name="command">The command to execute that requires a data stream</param> /// <param name="restart">Restart location in bytes for file transfer</param> /// <returns>A data stream ready to be used</returns> FtpDataStream OpenPassiveDataStream(FtpDataConnectionType type, string command, long restart) { FtpTrace.WriteFunc("OpenPassiveDataStream", new object[] { type, command, restart }); FtpDataStream stream = null; FtpReply reply; Match m; string host = null; int port = 0; if (m_stream == null) { throw new InvalidOperationException("The control connection stream is null! Generally this means there is no connection to the server. Cannot open a passive data stream."); } if (type == FtpDataConnectionType.EPSV || type == FtpDataConnectionType.AutoPassive) { if (!(reply = Execute("EPSV")).Success) { // if we're connected with IPv4 and data channel type is AutoPassive then fallback to IPv4 if (reply.Type == FtpResponseType.PermanentNegativeCompletion && type == FtpDataConnectionType.AutoPassive && m_stream != null && m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { return(OpenPassiveDataStream(FtpDataConnectionType.PASV, command, restart)); } throw new FtpCommandException(reply); } m = Regex.Match(reply.Message, @"\(\|\|\|(?<port>\d+)\|\)"); if (!m.Success) { throw new FtpException("Failed to get the EPSV port from: " + reply.Message); } host = m_host; port = int.Parse(m.Groups["port"].Value); } else { if (m_stream.LocalEndPoint.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) { throw new FtpException("Only IPv4 is supported by the PASV command. Use EPSV instead."); } if (!(reply = Execute("PASV")).Success) { throw new FtpCommandException(reply); } m = Regex.Match(reply.Message, @"(?<quad1>\d+)," + @"(?<quad2>\d+)," + @"(?<quad3>\d+)," + @"(?<quad4>\d+)," + @"(?<port1>\d+)," + @"(?<port2>\d+)"); if (!m.Success || m.Groups.Count != 7) { throw new FtpException(("Malformed PASV response: " + reply.Message)); } // PASVEX mode ignores the host supplied in the PASV response if (type == FtpDataConnectionType.PASVEX) { host = m_host; } else { host = (m.Groups["quad1"].Value + "." + m.Groups["quad2"].Value + "." + m.Groups["quad3"].Value + "." + m.Groups["quad4"].Value); } port = (int.Parse(m.Groups["port1"].Value) << 8) + int.Parse(m.Groups["port2"].Value); } stream = new FtpDataStream(this); stream.ConnectTimeout = DataConnectionConnectTimeout; stream.ReadTimeout = DataConnectionReadTimeout; Connect(stream, host, port, InternetProtocolVersions); stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); if (restart > 0) { if (!(reply = Execute("REST " + restart)).Success) { throw new FtpCommandException(reply); } } if (!(reply = Execute(command)).Success) { stream.Close(); throw new FtpCommandException(reply); } // the command status is used to determine // if a reply needs to be read from the server // when the stream is closed so always set it // otherwise things can get out of sync. stream.CommandStatus = reply; #if !NO_SSL // this needs to take place after the command is executed if (m_dataConnectionEncryption && m_encryptionmode != FtpEncryptionMode.None) { stream.ActivateEncryption(m_host, this.ClientCertificates.Count > 0 ? this.ClientCertificates : null, m_SslProtocols); } #endif return(stream); }
/// <summary> /// Opens the specified type of active data stream /// </summary> /// <param name="type">Type of passive data stream to open</param> /// <param name="command">The command to execute that requires a data stream</param> /// <param name="restart">Restart location in bytes for file transfer</param> /// <returns>A data stream ready to be used</returns> FtpDataStream OpenActiveDataStream(FtpDataConnectionType type, string command, long restart) { FtpTrace.WriteFunc("OpenActiveDataStream", new object[] { type, command, restart }); FtpDataStream stream = new FtpDataStream(this); FtpReply reply; #if !CORE IAsyncResult ar; #endif if (m_stream == null) { throw new InvalidOperationException("The control connection stream is null! Generally this means there is no connection to the server. Cannot open an active data stream."); } if (m_ActivePorts == null || !m_ActivePorts.Any()) { // Use random port stream.Listen(m_stream.LocalEndPoint.Address, 0); } else { var success = false; // Use one of the specified ports foreach (var port in m_ActivePorts) { try { stream.Listen(m_stream.LocalEndPoint.Address, port); success = true; } catch (SocketException se) { #if NETFX // Already in use if (se.ErrorCode != 10048) { throw; } #else throw; #endif } } // No usable port found if (!success) { throw new Exception("No valid active data port available!"); } } #if !CORE ar = stream.BeginAccept(null, null); #endif if (type == FtpDataConnectionType.EPRT || type == FtpDataConnectionType.AutoActive) { int ipver = 0; switch (stream.LocalEndPoint.AddressFamily) { case System.Net.Sockets.AddressFamily.InterNetwork: ipver = 1; // IPv4 break; case System.Net.Sockets.AddressFamily.InterNetworkV6: ipver = 2; // IPv6 break; default: throw new InvalidOperationException("The IP protocol being used is not supported."); } if (!(reply = Execute("EPRT |" + ipver + "|" + GetLocalAddress(stream.LocalEndPoint.Address) + "|" + stream.LocalEndPoint.Port + "|")).Success) { // if we're connected with IPv4 and the data channel type is AutoActive then try to fall back to the PORT command if (reply.Type == FtpResponseType.PermanentNegativeCompletion && type == FtpDataConnectionType.AutoActive && m_stream != null && m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { stream.ControlConnection = null; // we don't want this failed EPRT attempt to close our control connection when the stream is closed so clear out the reference. stream.Close(); return(OpenActiveDataStream(FtpDataConnectionType.PORT, command, restart)); } else { stream.Close(); throw new FtpCommandException(reply); } } } else { if (m_stream.LocalEndPoint.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) { throw new FtpException("Only IPv4 is supported by the PORT command. Use EPRT instead."); } if (!(reply = Execute("PORT " + GetLocalAddress(stream.LocalEndPoint.Address).Replace('.', ',') + "," + stream.LocalEndPoint.Port / 256 + "," + stream.LocalEndPoint.Port % 256)).Success) { stream.Close(); throw new FtpCommandException(reply); } } if (restart > 0) { if (!(reply = Execute("REST " + restart)).Success) { throw new FtpCommandException(reply); } } if (!(reply = Execute(command)).Success) { stream.Close(); throw new FtpCommandException(reply); } // the command status is used to determine // if a reply needs to be read from the server // when the stream is closed so always set it // otherwise things can get out of sync. stream.CommandStatus = reply; #if CORE stream.AcceptAsync().Wait(); #else ar.AsyncWaitHandle.WaitOne(m_dataConnectionConnectTimeout); if (!ar.IsCompleted) { stream.Close(); throw new TimeoutException("Timed out waiting for the server to connect to the active data socket."); } stream.EndAccept(ar); #endif #if !NO_SSL if (m_dataConnectionEncryption && m_encryptionmode != FtpEncryptionMode.None) { stream.ActivateEncryption(m_host, this.ClientCertificates.Count > 0 ? this.ClientCertificates : null, m_SslProtocols); } #endif stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); stream.ReadTimeout = m_dataConnectionReadTimeout; return(stream); }
/// <summary> /// Get the records of a file listing and retry if temporary failure. /// </summary> private async Task <List <string> > GetListingInternalAsync(string listcmd, bool retry, CancellationToken token) { List <string> rawlisting = new List <string>(); // always get the file listing in binary to avoid character translation issues with ASCII. await SetDataTypeNoLockAsync(FtpDataType.Binary, token); try { // read in raw file listing using (FtpDataStream stream = await OpenDataStreamAsync(listcmd, 0, token)) { try { this.LogLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); if (this.BulkListing) { // increases performance of GetListing by reading multiple lines of the file listing at once foreach (var line in await stream.ReadAllLinesAsync(Encoding, this.BulkListingLength, token)) { if (!FtpExtensions.IsNullOrWhiteSpace(line)) { rawlisting.Add(line); this.LogLine(FtpTraceLevel.Verbose, "Listing: " + line); } } } else { // GetListing will read file listings line-by-line (actually byte-by-byte) string buf; while ((buf = await stream.ReadLineAsync(Encoding, token)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); this.LogLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } } this.LogLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } } catch (FtpMissingSocketException) { // Some FTP server does not send any response when listing an empty directory // and the connection fails because no communication socket is provided by the server } catch (IOException ioEx) { // Some FTP servers forcibly close the connection, we absorb these errors // Fix #410: Retry if its a temporary failure ("Received an unexpected EOF or 0 bytes from the transport stream") if (retry && ioEx.Message.IsKnownError(unexpectedEOFStrings)) { // retry once more, but do not go into a infinite recursion loop here return(await GetListingInternalAsync(listcmd, false, token)); } else { // suppress all other types of exceptions } } return(rawlisting); }