1072 lines
39 KiB
C#
1072 lines
39 KiB
C#
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 {
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="System.Diagnostics.TraceListener"/>s for capturing
|
|
/// the conversation between FluentFTP and the server.
|
|
/// </summary>
|
|
/// <example>The following example illustrates how to assist in debugging
|
|
/// FluentFTP by getting a transaction log from the server.
|
|
/// <code source="..\Examples\Debug.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates adding a custom file
|
|
/// listing parser in the event that you encounter a list format
|
|
/// not already supported.
|
|
/// <code source="..\Examples\CustomParser.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to validate
|
|
/// a SSL certificate when using SSL/TLS.
|
|
/// <code source="..\Examples\ValidateCertificate.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to download a file.
|
|
/// <code source="..\Examples\OpenRead.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to download a file
|
|
/// using a URI object.
|
|
/// <code source="..\Examples\OpenReadURI.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to upload a file.
|
|
/// <code source="..\Examples\OpenWrite.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to upload a file
|
|
/// using a URI object.
|
|
/// <code source="..\Examples\OpenWriteURI.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to append to a file.
|
|
/// <code source="..\Examples\OpenAppend.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to append to a file
|
|
/// using a URI object.
|
|
/// <code source="..\Examples\OpenAppendURI.cs" lang="cs" />
|
|
/// </example>
|
|
/// <example>The following example demonstrates how to get a file
|
|
/// listing from the server.
|
|
/// <code source="..\Examples\GetListing.cs" lang="cs" />
|
|
/// </example>
|
|
public partial class FtpClient : IDisposable {
|
|
|
|
#region Properties
|
|
|
|
private FtpParser m_parser = FtpParser.Auto;
|
|
/// <summary>
|
|
/// File listing parser to be used.
|
|
/// Automatically calculated based on the type of the server, unless changed.
|
|
/// </summary>
|
|
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;
|
|
/// <summary>
|
|
/// Culture used to parse file listings
|
|
/// </summary>
|
|
public CultureInfo ListingCulture {
|
|
get { return m_parserCulture; }
|
|
set {
|
|
m_parserCulture = value;
|
|
|
|
// configure parser
|
|
m_listParser.parserCulture = value;
|
|
}
|
|
}
|
|
|
|
private double m_timeDiff = 0;
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Check if your server supports a recursive LIST command (LIST -R).
|
|
/// If you know for sure that this is unsupported, set it to false.
|
|
/// </summary>
|
|
public bool RecursiveList {
|
|
get {
|
|
if (SystemType.StartsWith("Windows_CE")) {
|
|
return false;
|
|
}
|
|
return m_recursiveList;
|
|
}
|
|
set {
|
|
m_recursiveList = value;
|
|
}
|
|
}
|
|
|
|
private bool m_bulkListing = true;
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="BulkListingLength"/>.
|
|
/// </summary>
|
|
public bool BulkListing {
|
|
get {
|
|
return m_bulkListing;
|
|
}
|
|
set {
|
|
m_bulkListing = value;
|
|
}
|
|
}
|
|
|
|
private int m_bulkListingLength = 128;
|
|
|
|
/// <summary>
|
|
/// Bytes to read during GetListing. Only honored if <see cref="BulkListing"/> is true.
|
|
/// </summary>
|
|
public int BulkListingLength {
|
|
get {
|
|
return m_bulkListingLength;
|
|
}
|
|
set {
|
|
m_bulkListingLength = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Get File Info
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="path">The path of the file or folder</param>
|
|
/// <param name="dateModified">Get the accurate modified date using another MDTM command</param>
|
|
/// <returns>A FtpListItem object</returns>
|
|
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);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to return information about a remote file system object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// You should check the <see cref="Capabilities"/> property for the <see cref="FtpCapability.MLSD"/>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="path">Path of the file or folder</param>
|
|
/// <param name="dateModified">Get the accurate modified date using another MDTM command</param>
|
|
/// <param name="callback">Async Callback</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends a call to <see cref="BeginGetObjectInfo"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetObjectInfo"/></param>
|
|
/// <returns>A <see cref="FtpListItem"/> if the command succeeded, or null if there was a problem.</returns>
|
|
public FtpListItem EndGetObjectInfo(IAsyncResult ar) {
|
|
return GetAsyncDelegate<AsyncGetObjectInfo>(ar).EndInvoke(ar);
|
|
}
|
|
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Return information about a remote file system object asynchronously.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// You should check the <see cref="Capabilities"/> property for the <see cref="FtpCapability.MLSD"/>
|
|
/// 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.</remarks>
|
|
/// <param name="path">Path of the item to retrieve information about</param>
|
|
/// <param name="dateModified">Get the accurate modified date using another MDTM command</param>
|
|
/// <exception cref="InvalidOperationException">Thrown if the server does not support this Capability</exception>
|
|
/// <returns>A <see cref="FtpListItem"/> if the command succeeded, or null if there was a problem.</returns>
|
|
public async Task<FtpListItem> GetObjectInfoAsync(string path, bool dateModified = false) {
|
|
|
|
//TODO: Rewrite as true async method with cancellation support
|
|
return await Task.Factory.FromAsync<string, bool, FtpListItem>(
|
|
(p, dm, ac, s) => BeginGetObjectInfo(p, dm, ac, s),
|
|
ar => EndGetObjectInfo(ar),
|
|
path, dateModified, null);
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Get Listing
|
|
|
|
/// <summary>
|
|
/// Gets a file listing from the server from the current working directory. 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>
|
|
/// <returns>An array of FtpListItem objects</returns>
|
|
/// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example>
|
|
public FtpListItem[] GetListing() {
|
|
return GetListing(null);
|
|
}
|
|
|
|
/// <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>
|
|
/// <returns>An array of FtpListItem objects</returns>
|
|
/// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example>
|
|
public FtpListItem[] GetListing(string path) {
|
|
return GetListing(path, 0);
|
|
}
|
|
|
|
/// <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, "+---------------------------------------+");
|
|
|
|
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
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to get 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="callback">AsyncCallback method</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
|
|
public IAsyncResult BeginGetListing(AsyncCallback callback, Object state) {
|
|
return BeginGetListing(null, callback, state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to get 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 to list</param>
|
|
/// <param name="callback">AsyncCallback method</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
|
|
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);
|
|
|
|
/// <summary>
|
|
/// Gets a file listing from the server asynchronously
|
|
/// </summary>
|
|
/// <param name="path">The path to list</param>
|
|
/// <param name="options">Options that dictate how the list operation is performed</param>
|
|
/// <param name="callback">AsyncCallback method</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends a call to <see cref="o:BeginGetListing"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult return from <see cref="o:BeginGetListing"/></param>
|
|
/// <returns>An array of items retrieved in the listing</returns>
|
|
/// <example><code source="..\Examples\BeginGetListing.cs" lang="cs" /></example>
|
|
public FtpListItem[] EndGetListing(IAsyncResult ar) {
|
|
return GetAsyncDelegate<AsyncGetListing>(ar).EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <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>
|
|
/// <returns>An array of items retrieved in the listing</returns>
|
|
public async Task<FtpListItem[]> GetListingAsync(string path, FtpListOption options)
|
|
{
|
|
//TODO: Add cancellation support
|
|
FtpTrace.WriteFunc(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);
|
|
|
|
// 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();
|
|
}
|
|
|
|
/// <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>
|
|
/// <returns>An array of items retrieved in the listing</returns>
|
|
public Task<FtpListItem[]> GetListingAsync(string path) {
|
|
//TODO: Add cancellation support
|
|
return GetListingAsync(path, 0);
|
|
}
|
|
|
|
/// <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>
|
|
/// <returns>An array of items retrieved in the listing</returns>
|
|
public Task<FtpListItem[]> GetListingAsync() {
|
|
//TODO: Add cancellation support
|
|
return GetListingAsync(null);
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Get Name Listing
|
|
|
|
/// <summary>
|
|
/// Returns a file/directory listing using the NLST command.
|
|
/// </summary>
|
|
/// <returns>A string array of file and directory names if any were returned.</returns>
|
|
public string[] GetNameListing() {
|
|
return GetNameListing(null);
|
|
}
|
|
|
|
/// <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) {
|
|
|
|
FtpTrace.WriteFunc("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();
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string[] AsyncGetNameListing(string path);
|
|
|
|
/// <summary>
|
|
/// Begin an asynchronous operation to return a file/directory listing using the NLST command.
|
|
/// </summary>
|
|
/// <param name="path">The path of the directory to list</param>
|
|
/// <param name="callback">Async Callback</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
/// <example><code source="..\Examples\BeginGetNameListing.cs" lang="cs" /></example>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin an asynchronous operation to return a file/directory listing using the NLST command.
|
|
/// </summary>
|
|
/// <param name="callback">Async Callback</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
/// <example><code source="..\Examples\BeginGetNameListing.cs" lang="cs" /></example>
|
|
public IAsyncResult BeginGetNameListing(AsyncCallback callback, object state) {
|
|
return BeginGetNameListing(null, callback, state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends a call to <see cref="o:BeginGetNameListing"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult object returned from <see cref="o:BeginGetNameListing"/></param>
|
|
/// <returns>An array of file and directory names if any were returned.</returns>
|
|
/// <example><code source="..\Examples\BeginGetNameListing.cs" lang="cs" /></example>
|
|
public string[] EndGetNameListing(IAsyncResult ar) {
|
|
return GetAsyncDelegate<AsyncGetNameListing>(ar).EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Returns a file/directory listing using the NLST command asynchronously
|
|
/// </summary>
|
|
/// <param name="path">The path of the directory to list</param>
|
|
/// <returns>An array of file and directory names if any were returned.</returns>
|
|
public async Task<string[]> GetNameListingAsync(string path)
|
|
{
|
|
//TODO: Add cancellation support
|
|
FtpTrace.WriteFunc(nameof(GetNameListingAsync), new object[] { path });
|
|
|
|
List<string> listing = new List<string>();
|
|
|
|
// 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a file/directory listing using the NLST command asynchronously
|
|
/// </summary>
|
|
/// <returns>An array of file and directory names if any were returned.</returns>
|
|
public Task<string[]> GetNameListingAsync() {
|
|
//TODO: Add cancellation support
|
|
return GetNameListingAsync(null);
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |