using System; using System.IO; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Reflection; using System.Collections.Generic; using System.Diagnostics; using System.Security.Cryptography.X509Certificates; using System.Globalization; using System.Security.Authentication; using System.Net; using FluentFTP.Proxy; #if !CORE using System.Web; #endif #if (CORE || NETFX) using System.Threading; #endif #if ASYNC using System.Threading.Tasks; #endif namespace FluentFTP { /// /// FTP Control Connection. Speaks the FTP protocol with the server and /// provides facilities for performing transactions. /// /// Debugging problems with FTP transactions is much easier to do when /// you can see exactly what is sent to the server and the reply /// FluentFTP gets in return. Please review the Debug example /// below for information on how to add s for capturing /// the conversation between FluentFTP and the server. /// /// The following example illustrates how to assist in debugging /// FluentFTP by getting a transaction log from the server. /// /// /// The following example demonstrates adding a custom file /// listing parser in the event that you encounter a list format /// not already supported. /// /// /// The following example demonstrates how to validate /// a SSL certificate when using SSL/TLS. /// /// /// The following example demonstrates how to download a file. /// /// /// The following example demonstrates how to download a file /// using a URI object. /// /// /// The following example demonstrates how to upload a file. /// /// /// The following example demonstrates how to upload a file /// using a URI object. /// /// /// The following example demonstrates how to append to a file. /// /// /// The following example demonstrates how to append to a file /// using a URI object. /// /// /// The following example demonstrates how to get a file /// listing from the server. /// /// public partial class FtpClient : IDisposable { #region Properties private FtpParser m_parser = FtpParser.Auto; /// /// File listing parser to be used. /// Automatically calculated based on the type of the server, unless changed. /// public FtpParser ListingParser { get { return m_parser; } set { m_parser = value; // configure parser m_listParser.parser = value; m_listParser.parserConfirmed = false; } } private CultureInfo m_parserCulture = CultureInfo.InvariantCulture; /// /// Culture used to parse file listings /// public CultureInfo ListingCulture { get { return m_parserCulture; } set { m_parserCulture = value; // configure parser m_listParser.parserCulture = value; } } private double m_timeDiff = 0; /// /// Time difference between server and client, in hours. /// If the server is located in New York and you are in London then the time difference is -5 hours. /// public double TimeOffset { get { return m_timeDiff; } set { m_timeDiff = value; // configure parser int hours = (int)Math.Floor(m_timeDiff); int mins = (int)Math.Floor((m_timeDiff - Math.Floor(m_timeDiff)) * 60); m_listParser.timeOffset = new TimeSpan(hours, mins, 0); m_listParser.hasTimeOffset = m_timeDiff != 0; } } private bool m_recursiveList = true; /// /// Check if your server supports a recursive LIST command (LIST -R). /// If you know for sure that this is unsupported, set it to false. /// public bool RecursiveList { get { if (SystemType.StartsWith("Windows_CE")) { return false; } return m_recursiveList; } set { m_recursiveList = value; } } private bool m_bulkListing = true; /// /// If true, increases performance of GetListing by reading multiple lines /// of the file listing at once. If false then GetListing will read file /// listings line-by-line. If GetListing is having issues with your server, /// set it to false. /// /// The number of bytes read is based upon . /// public bool BulkListing { get { return m_bulkListing; } set { m_bulkListing = value; } } private int m_bulkListingLength = 128; /// /// Bytes to read during GetListing. Only honored if is true. /// public int BulkListingLength { get { return m_bulkListingLength; } set { m_bulkListingLength = value; } } #endregion #region Get File Info /// /// Returns information about a file system object. Returns null if the server response can't /// be parsed or the server returns a failure completion code. The error for a failure /// is logged with FtpTrace. No exception is thrown on error because that would negate /// the usefulness of this method for checking for the existence of an object. /// /// The path of the file or folder /// Get the accurate modified date using another MDTM command /// A FtpListItem object public FtpListItem GetObjectInfo(string path, bool dateModified = false) { // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc("GetObjectInfo", new object[] { path, dateModified }); FtpReply reply; string[] res; bool supportsMachineList = (Capabilities & FtpCapability.MLSD) == FtpCapability.MLSD; FtpListItem result = null; if (supportsMachineList) { // USE MACHINE LISTING TO GET INFO FOR A SINGLE FILE if ((reply = Execute("MLST " + path)).Success) { res = reply.InfoMessages.Split('\n'); if (res.Length > 1) { string info = ""; for (int i = 1; i < res.Length; i++) { info += res[i]; } result = m_listParser.ParseSingleLine(null, info, m_caps, true); } } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " with error " + reply.ErrorMessage); } } else { // USE GETLISTING TO GET ALL FILES IN DIR .. SLOWER BUT AT LEAST IT WORKS string dirPath = path.GetFtpDirectoryName(); FtpListItem[] dirItems = GetListing(dirPath); foreach (var dirItem in dirItems) { if (dirItem.FullName == path) { result = dirItem; break; } } FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " since MLST not supported and GetListing() fails to list file/folder."); } // Get the accurate date modified using another MDTM command if (result != null && dateModified && HasFeature(FtpCapability.MDTM)) { result.Modified = GetModifiedTime(path); } return result; } delegate FtpListItem AsyncGetObjectInfo(string path, bool dateModified); /// /// Begins an asynchronous operation to return information about a remote file system object. /// /// /// You should check the property for the /// flag before calling this method. Failing to do so will result in an InvalidOperationException /// being thrown when the server does not support machine listings. Returns null if the server response can't /// be parsed or the server returns a failure completion code. The error for a failure /// is logged with FtpTrace. No exception is thrown on error because that would negate /// the usefulness of this method for checking for the existence of an object. /// /// Path of the file or folder /// Get the accurate modified date using another MDTM command /// Async Callback /// State object /// IAsyncResult public IAsyncResult BeginGetObjectInfo(string path, bool dateModified, AsyncCallback callback, object state) { IAsyncResult ar; AsyncGetObjectInfo func; ar = (func = new AsyncGetObjectInfo(GetObjectInfo)).BeginInvoke(path, dateModified, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// IAsyncResult returned from /// A if the command succeeded, or null if there was a problem. public FtpListItem EndGetObjectInfo(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #if ASYNC /// /// Return information about a remote file system object asynchronously. /// /// /// You should check the property for the /// flag before calling this method. Failing to do so will result in an InvalidOperationException /// being thrown when the server does not support machine listings. Returns null if the server response can't /// be parsed or the server returns a failure completion code. The error for a failure /// is logged with FtpTrace. No exception is thrown on error because that would negate /// the usefulness of this method for checking for the existence of an object. /// Path of the item to retrieve information about /// Get the accurate modified date using another MDTM command /// Thrown if the server does not support this Capability /// A if the command succeeded, or null if there was a problem. public async Task GetObjectInfoAsync(string path, bool dateModified = false) { //TODO: Rewrite as true async method with cancellation support return await Task.Factory.FromAsync( (p, dm, ac, s) => BeginGetObjectInfo(p, dm, ac, s), ar => EndGetObjectInfo(ar), path, dateModified, null); } #endif #endregion #region Get Listing /// /// Gets a file listing from the server from the current working directory. Each object returned /// contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// An array of FtpListItem objects /// public FtpListItem[] GetListing() { return GetListing(null); } /// /// Gets a file listing from the server. Each object returned /// contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// The path of the directory to list /// An array of FtpListItem objects /// public FtpListItem[] GetListing(string path) { return GetListing(path, 0); } /// /// Gets a file listing from the server. Each object returned /// contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// The path of the directory to list /// Options that dictacte how a list is performed and what information is gathered. /// An array of FtpListItem objects /// public FtpListItem[] GetListing(string path, FtpListOption options) { FtpTrace.WriteFunc("GetListing", new object[] { path, options }); FtpListItem item = null; List lst = new List(); List rawlisting = new List(); 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, "+---------------------------------------+"); if (this.BulkListing) { // increases performance of GetListing by reading multiple lines of the file listing at once foreach (var line in stream.ReadAllLines(Encoding, this.BulkListingLength)) { if (!FtpExtensions.IsNullOrWhiteSpace(line)) { rawlisting.Add(line); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + line); } } } else { // GetListing will read file listings line-by-line (actually byte-by-byte) 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]; try { item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList); } catch (FtpListParser.CriticalListParseException) { FtpTrace.WriteStatus(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 { //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(); } #if !CORE /// /// Begins an asynchronous operation to get a file listing from the server. /// Each object returned contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// AsyncCallback method /// State object /// IAsyncResult /// public IAsyncResult BeginGetListing(AsyncCallback callback, Object state) { return BeginGetListing(null, callback, state); } /// /// Begins an asynchronous operation to get a file listing from the server. /// Each object returned contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// The path to list /// AsyncCallback method /// State object /// IAsyncResult /// public IAsyncResult BeginGetListing(string path, AsyncCallback callback, Object state) { return BeginGetListing(path, FtpListOption.Modify | FtpListOption.Size, callback, state); } delegate FtpListItem[] AsyncGetListing(string path, FtpListOption options); /// /// Gets a file listing from the server asynchronously /// /// The path to list /// Options that dictate how the list operation is performed /// AsyncCallback method /// State object /// IAsyncResult /// public IAsyncResult BeginGetListing(string path, FtpListOption options, AsyncCallback callback, Object state) { IAsyncResult ar; AsyncGetListing func; ar = (func = new AsyncGetListing(GetListing)).BeginInvoke(path, options, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// IAsyncResult return from /// An array of items retrieved in the listing /// public FtpListItem[] EndGetListing(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Gets a file listing from the server asynchronously. Each object returned /// contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// The path to list /// Options that dictate how the list operation is performed /// An array of items retrieved in the listing public async Task GetListingAsync(string path, FtpListOption options) { //TODO: Add cancellation support FtpTrace.WriteFunc(nameof(GetListingAsync), new object[] { path, options }); FtpListItem item = null; List lst = new List(); List rawlisting = new List(); 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); // 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"); // read in raw file listing using (FtpDataStream stream = await OpenDataStreamAsync(listcmd, 0)) { try { FtpTrace.WriteLine(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)) { if (!FtpExtensions.IsNullOrWhiteSpace(line)) { rawlisting.Add(line); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + line); } } } else { // GetListing will read file listings line-by-line (actually byte-by-byte) while ((buf = await stream.ReadLineAsync(Encoding)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } } FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } 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)) 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) { FtpTrace.WriteStatus(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 { //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 = await DereferenceLinkAsync(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 = await GetModifiedTimeAsync(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 = await GetFileSizeAsync(item.FullName); } else { item.Size = 0; } } } } } return lst.ToArray(); } /// /// Gets a file listing from the server asynchronously. Each object returned /// contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// The path to list /// An array of items retrieved in the listing public Task GetListingAsync(string path) { //TODO: Add cancellation support return GetListingAsync(path, 0); } /// /// Gets a file listing from the server asynchronously. Each object returned /// contains information about the file that was able to be retrieved. /// /// /// If a property is equal to then it means the /// date in question was not able to be retrieved. If the property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// /// An array of items retrieved in the listing public Task GetListingAsync() { //TODO: Add cancellation support return GetListingAsync(null); } #endif #endregion #region Get Name Listing /// /// Returns a file/directory listing using the NLST command. /// /// A string array of file and directory names if any were returned. public string[] GetNameListing() { return GetNameListing(null); } /// /// Returns a file/directory listing using the NLST command. /// /// The path of the directory to list /// A string array of file and directory names if any were returned. /// public string[] GetNameListing(string path) { FtpTrace.WriteFunc("GetNameListing", new object[] { path }); List listing = new List(); // 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(); } #if !CORE delegate string[] AsyncGetNameListing(string path); /// /// Begin an asynchronous operation to return a file/directory listing using the NLST command. /// /// The path of the directory to list /// Async Callback /// State object /// IAsyncResult /// public IAsyncResult BeginGetNameListing(string path, AsyncCallback callback, object state) { IAsyncResult ar; AsyncGetNameListing func; ar = (func = new AsyncGetNameListing(GetNameListing)).BeginInvoke(path, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Begin an asynchronous operation to return a file/directory listing using the NLST command. /// /// Async Callback /// State object /// IAsyncResult /// public IAsyncResult BeginGetNameListing(AsyncCallback callback, object state) { return BeginGetNameListing(null, callback, state); } /// /// Ends a call to /// /// IAsyncResult object returned from /// An array of file and directory names if any were returned. /// public string[] EndGetNameListing(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Returns a file/directory listing using the NLST command asynchronously /// /// The path of the directory to list /// An array of file and directory names if any were returned. public async Task GetNameListingAsync(string path) { //TODO: Add cancellation support FtpTrace.WriteFunc(nameof(GetNameListingAsync), new object[] { path }); List listing = new List(); // calc path to request path = await GetAbsolutePathAsync(path); // always get the file listing in binary // to avoid any potential character translation // problems that would happen if in ASCII. await ExecuteAsync("TYPE I"); using (FtpDataStream stream = await OpenDataStreamAsync(("NLST " + path.GetFtpPath()), 0)) { string buf; try { while ((buf = await stream.ReadLineAsync(Encoding)) != null) listing.Add(buf); } finally { stream.Close(); } } return listing.ToArray(); } /// /// Returns a file/directory listing using the NLST command asynchronously /// /// An array of file and directory names if any were returned. public Task GetNameListingAsync() { //TODO: Add cancellation support return GetNameListingAsync(null); } #endif #endregion } }