1162 lines
39 KiB
C#
1162 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.Linq;
|
|
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 File Hashing - HASH
|
|
|
|
/// <summary>
|
|
/// Gets the currently selected hash algorithm for the HASH command.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This feature is experimental. See this link for details:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <returns>The <see cref="FtpHashAlgorithm"/> flag or <see cref="FtpHashAlgorithm.NONE"/> if there was a problem.</returns>
|
|
/// <example><code source="..\Examples\GetHashAlgorithm.cs" lang="cs" /></example>
|
|
public FtpHashAlgorithm GetHashAlgorithm() {
|
|
FtpReply reply;
|
|
FtpHashAlgorithm type = FtpHashAlgorithm.NONE;
|
|
|
|
#if !CORE14
|
|
lock (m_lock) {
|
|
#endif
|
|
if ((reply = Execute("OPTS HASH")).Success) {
|
|
switch (reply.Message) {
|
|
case "SHA-1":
|
|
type = FtpHashAlgorithm.SHA1;
|
|
break;
|
|
case "SHA-256":
|
|
type = FtpHashAlgorithm.SHA256;
|
|
break;
|
|
case "SHA-512":
|
|
type = FtpHashAlgorithm.SHA512;
|
|
break;
|
|
case "MD5":
|
|
type = FtpHashAlgorithm.MD5;
|
|
break;
|
|
}
|
|
}
|
|
#if !CORE14
|
|
}
|
|
#endif
|
|
|
|
return type;
|
|
}
|
|
|
|
delegate FtpHashAlgorithm AsyncGetHashAlgorithm();
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to get the currently selected hash algorithm for the HASH command.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This feature is experimental. See this link for details:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <param name="callback">Async callback</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetHashAlgorithm(AsyncCallback callback, object state) {
|
|
AsyncGetHashAlgorithm func;
|
|
IAsyncResult ar;
|
|
|
|
ar = (func = new AsyncGetHashAlgorithm(GetHashAlgorithm)).BeginInvoke(callback, state);
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends a call to <see cref="BeginGetHashAlgorithm"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetHashAlgorithm"/></param>
|
|
/// <returns>The <see cref="FtpHashAlgorithm"/> flag or <see cref="FtpHashAlgorithm.NONE"/> if there was a problem.</returns>
|
|
public FtpHashAlgorithm EndGetHashAlgorithm(IAsyncResult ar) {
|
|
return GetAsyncDelegate<AsyncGetHashAlgorithm>(ar).EndInvoke(ar);
|
|
}
|
|
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the currently selected hash algorithm for the HASH command asynchronously.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This feature is experimental. See this link for details:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <returns>The <see cref="FtpHashAlgorithm"/> flag or <see cref="FtpHashAlgorithm.NONE"/> if there was a problem.</returns>
|
|
public async Task<FtpHashAlgorithm> GetHashAlgorithmAsync() {
|
|
//TODO: Rewrite as true async method with cancellation support
|
|
return await Task.Factory.FromAsync<FtpHashAlgorithm>(
|
|
(ac, s) => BeginGetHashAlgorithm(ac, s),
|
|
ar => EndGetHashAlgorithm(ar), null);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Sets the hash algorithm on the server to use for the HASH command.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If you specify an algorithm not listed in <see cref="FtpClient.HashAlgorithms"/>
|
|
/// a <see cref="NotImplementedException"/> will be thrown
|
|
/// so be sure to query that list of Flags before
|
|
/// selecting a hash algorithm. Support for the
|
|
/// HASH command is experimental. Please see
|
|
/// the following link for more details:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <param name="type">Hash Algorithm</param>
|
|
/// <exception cref="System.NotImplementedException">Thrown if the selected algorithm is not available on the server</exception>
|
|
/// <example><code source="..\Examples\SetHashAlgorithm.cs" lang="cs" /></example>
|
|
public void SetHashAlgorithm(FtpHashAlgorithm type) {
|
|
FtpReply reply;
|
|
string algorithm;
|
|
|
|
#if !CORE14
|
|
lock (m_lock) {
|
|
#endif
|
|
if ((HashAlgorithms & type) != type)
|
|
throw new NotImplementedException(("The hash algorithm " + type.ToString() + " was not advertised by the server."));
|
|
|
|
switch (type) {
|
|
case FtpHashAlgorithm.SHA1:
|
|
algorithm = "SHA-1";
|
|
break;
|
|
case FtpHashAlgorithm.SHA256:
|
|
algorithm = "SHA-256";
|
|
break;
|
|
case FtpHashAlgorithm.SHA512:
|
|
algorithm = "SHA-512";
|
|
break;
|
|
case FtpHashAlgorithm.MD5:
|
|
algorithm = "MD5";
|
|
break;
|
|
default:
|
|
algorithm = type.ToString();
|
|
break;
|
|
}
|
|
|
|
if (!(reply = Execute("OPTS HASH " + algorithm)).Success)
|
|
throw new FtpCommandException(reply);
|
|
#if !CORE14
|
|
}
|
|
#endif
|
|
}
|
|
|
|
delegate void AsyncSetHashAlgorithm(FtpHashAlgorithm type);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to set the hash algorithm on the server to use for the HASH command.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If you specify an algorithm not listed in <see cref="FtpClient.HashAlgorithms"/>
|
|
/// a <see cref="NotImplementedException"/> will be thrown
|
|
/// so be sure to query that list of Flags before
|
|
/// selecting a hash algorithm. Support for the
|
|
/// HASH command is experimental. Please see
|
|
/// the following link for more details:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <param name="type">Hash algorithm to use</param>
|
|
/// <param name="callback">Async Callback</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginSetHashAlgorithm(FtpHashAlgorithm type, AsyncCallback callback, object state) {
|
|
AsyncSetHashAlgorithm func;
|
|
IAsyncResult ar;
|
|
|
|
ar = (func = new AsyncSetHashAlgorithm(SetHashAlgorithm)).BeginInvoke(type, callback, state);
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginSetHashAlgorithm"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginSetHashAlgorithm"/></param>
|
|
public void EndSetHashAlgorithm(IAsyncResult ar) {
|
|
GetAsyncDelegate<AsyncSetHashAlgorithm>(ar).EndInvoke(ar);
|
|
}
|
|
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Sets the hash algorithm on the server to be used with the HASH command asynchronously.
|
|
/// </summary>
|
|
/// <param name="type">Hash algorithm to use</param>
|
|
/// <exception cref="System.NotImplementedException">Thrown if the selected algorithm is not available on the server</exception>
|
|
public async Task SetHashAlgorithmAsync(FtpHashAlgorithm type) {
|
|
//TODO: Rewrite as true async method with cancellation support
|
|
await Task.Factory.FromAsync<FtpHashAlgorithm>(
|
|
(t, ac, s) => BeginSetHashAlgorithm(t, ac, s),
|
|
ar => EndSetHashAlgorithm(ar),
|
|
type, null);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Gets the hash of an object on the server using the currently selected hash algorithm.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/>
|
|
/// property. You should confirm that it's not equal
|
|
/// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method
|
|
/// otherwise the server trigger a <see cref="FtpCommandException"/>
|
|
/// due to a lack of support for the HASH command. You can
|
|
/// set the algorithm using the <see cref="SetHashAlgorithm"/> method and
|
|
/// you can query the server for the current hash algorithm
|
|
/// using the <see cref="GetHashAlgorithm"/> method.
|
|
///
|
|
/// This feature is experimental and based on the following draft:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <param name="path">Full or relative path of the object to compute the hash for.</param>
|
|
/// <returns>The hash of the file.</returns>
|
|
/// <exception cref="FtpCommandException">
|
|
/// Thrown if the <see cref="HashAlgorithms"/> property is <see cref="FtpHashAlgorithm.NONE"/>,
|
|
/// the remote path does not exist, or the command cannot be executed.
|
|
/// </exception>
|
|
/// <exception cref="ArgumentException">Path argument is null</exception>
|
|
/// <exception cref="NotImplementedException">Thrown when an unknown hash algorithm type is returned by the server</exception>
|
|
/// <example><code source="..\Examples\GetHash.cs" lang="cs" /></example>
|
|
public FtpHash GetHash(string path) {
|
|
FtpReply reply;
|
|
FtpHash hash = new FtpHash();
|
|
Match m;
|
|
|
|
if (path == null)
|
|
throw new ArgumentException("GetHash(path) argument can't be null");
|
|
|
|
#if !CORE14
|
|
lock (m_lock) {
|
|
#endif
|
|
if (!(reply = Execute("HASH " + path.GetFtpPath())).Success)
|
|
throw new FtpCommandException(reply);
|
|
#if !CORE14
|
|
}
|
|
#endif
|
|
|
|
// Current draft says the server should return this:
|
|
// SHA-256 0-49 169cd22282da7f147cb491e559e9dd filename.ext
|
|
if (!(m = Regex.Match(reply.Message,
|
|
@"(?<algorithm>.+)\s" +
|
|
@"(?<bytestart>\d+)-(?<byteend>\d+)\s" +
|
|
@"(?<hash>.+)\s" +
|
|
@"(?<filename>.+)")).Success) {
|
|
|
|
// Current version of FileZilla returns this:
|
|
// SHA-1 21c2ca15cf570582949eb59fb78038b9c27ffcaf
|
|
m = Regex.Match(reply.Message, @"(?<algorithm>.+)\s(?<hash>.+)\s");
|
|
}
|
|
|
|
if (m != null && m.Success) {
|
|
switch (m.Groups["algorithm"].Value) {
|
|
case "SHA-1":
|
|
hash.Algorithm = FtpHashAlgorithm.SHA1;
|
|
break;
|
|
case "SHA-256":
|
|
hash.Algorithm = FtpHashAlgorithm.SHA256;
|
|
break;
|
|
case "SHA-512":
|
|
hash.Algorithm = FtpHashAlgorithm.SHA512;
|
|
break;
|
|
case "MD5":
|
|
hash.Algorithm = FtpHashAlgorithm.MD5;
|
|
break;
|
|
default:
|
|
throw new NotImplementedException("Unknown hash algorithm: " + m.Groups["algorithm"].Value);
|
|
}
|
|
|
|
hash.Value = m.Groups["hash"].Value;
|
|
} else {
|
|
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse hash from: " + reply.Message);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate FtpHash AsyncGetHash(string path);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to get the hash of an object on the server using the currently selected hash algorithm.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/>
|
|
/// property. You should confirm that it's not equal
|
|
/// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method
|
|
/// otherwise the server trigger a <see cref="FtpCommandException"/>
|
|
/// due to a lack of support for the HASH command. You can
|
|
/// set the algorithm using the <see cref="SetHashAlgorithm"/> method and
|
|
/// you can query the server for the current hash algorithm
|
|
/// using the <see cref="GetHashAlgorithm"/> method.
|
|
///
|
|
/// This feature is experimental and based on the following draft:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <param name="path">The file you want the server to compute the hash for</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetHash(string path, AsyncCallback callback, object state) {
|
|
AsyncGetHash func;
|
|
IAsyncResult ar;
|
|
|
|
ar = (func = new AsyncGetHash(GetHash)).BeginInvoke(path, callback, state);
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetHash"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetHash"/></param>
|
|
public FtpHash EndGetHash(IAsyncResult ar) {
|
|
return GetAsyncDelegate<AsyncGetHash>(ar).EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the hash of an object on the server using the currently selected hash algorithm asynchronously.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/>
|
|
/// property. You should confirm that it's not equal
|
|
/// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method
|
|
/// otherwise the server trigger a <see cref="FtpCommandException"/>
|
|
/// due to a lack of support for the HASH command. You can
|
|
/// set the algorithm using the <see cref="SetHashAlgorithm"/> method and
|
|
/// you can query the server for the current hash algorithm
|
|
/// using the <see cref="GetHashAlgorithm"/> method.
|
|
///
|
|
/// This feature is experimental and based on the following draft:
|
|
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
|
|
/// </remarks>
|
|
/// <param name="path">The file you want the server to compute the hash for</param>
|
|
/// <exception cref="FtpCommandException">
|
|
/// Thrown if the <see cref="HashAlgorithms"/> property is <see cref="FtpHashAlgorithm.NONE"/>,
|
|
/// the remote path does not exist, or the command cannot be executed.
|
|
/// </exception>
|
|
/// <exception cref="ArgumentException">Path argument is null</exception>
|
|
/// <exception cref="NotImplementedException">Thrown when an unknown hash algorithm type is returned by the server</exception>
|
|
/// <returns>The hash of the file.</returns>
|
|
public async Task<FtpHash> GetHashAsync(string path) {
|
|
//TODO: Add cancellation support
|
|
FtpReply reply;
|
|
FtpHash hash = new FtpHash();
|
|
Match m;
|
|
|
|
if (path == null)
|
|
throw new ArgumentException("GetHash(path) argument can't be null");
|
|
|
|
if (!(reply = await ExecuteAsync("HASH " + path.GetFtpPath())).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
// Current draft says the server should return this:
|
|
// SHA-256 0-49 169cd22282da7f147cb491e559e9dd filename.ext
|
|
if (!(m = Regex.Match(reply.Message,
|
|
@"(?<algorithm>.+)\s" +
|
|
@"(?<bytestart>\d+)-(?<byteend>\d+)\s" +
|
|
@"(?<hash>.+)\s" +
|
|
@"(?<filename>.+)")).Success)
|
|
{
|
|
|
|
// Current version of FileZilla returns this:
|
|
// SHA-1 21c2ca15cf570582949eb59fb78038b9c27ffcaf
|
|
m = Regex.Match(reply.Message, @"(?<algorithm>.+)\s(?<hash>.+)\s");
|
|
}
|
|
|
|
if (m != null && m.Success)
|
|
{
|
|
switch (m.Groups["algorithm"].Value)
|
|
{
|
|
case "SHA-1":
|
|
hash.Algorithm = FtpHashAlgorithm.SHA1;
|
|
break;
|
|
case "SHA-256":
|
|
hash.Algorithm = FtpHashAlgorithm.SHA256;
|
|
break;
|
|
case "SHA-512":
|
|
hash.Algorithm = FtpHashAlgorithm.SHA512;
|
|
break;
|
|
case "MD5":
|
|
hash.Algorithm = FtpHashAlgorithm.MD5;
|
|
break;
|
|
default:
|
|
throw new NotImplementedException("Unknown hash algorithm: " + m.Groups["algorithm"].Value);
|
|
}
|
|
|
|
hash.Value = m.Groups["hash"].Value;
|
|
}
|
|
else
|
|
{
|
|
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse hash from: " + reply.Message);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region File Checksum
|
|
|
|
/// <summary>
|
|
/// Retrieves a checksum of the given file using a checksum method that the server supports, if any.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The algorithm used goes in this order:
|
|
/// 1. HASH command; server preferred algorithm. See <see cref="FtpClient.SetHashAlgorithm"/>
|
|
/// 2. MD5 / XMD5 commands
|
|
/// 3. XSHA1 command
|
|
/// 4. XSHA256 command
|
|
/// 5. XSHA512 command
|
|
/// 6. XCRC command
|
|
/// </remarks>
|
|
/// <param name="path">Full or relative path of the file to checksum</param>
|
|
/// <returns><see cref="FtpHash"/> object containing the value and algorithm. Use the <see cref="FtpHash.IsValid"/> property to
|
|
/// determine if this command was successful. <see cref="FtpCommandException"/>s can be thrown from
|
|
/// the underlying calls.</returns>
|
|
/// <example><code source="..\Examples\GetChecksum.cs" lang="cs" /></example>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public FtpHash GetChecksum(string path) {
|
|
if (HasFeature(FtpCapability.HASH)) {
|
|
return GetHash(path);
|
|
} else {
|
|
FtpHash res = new FtpHash();
|
|
|
|
if (HasFeature(FtpCapability.MD5)) {
|
|
res.Value = GetMD5(path);
|
|
res.Algorithm = FtpHashAlgorithm.MD5;
|
|
} else if (HasFeature(FtpCapability.XMD5)) {
|
|
res.Value = GetXMD5(path);
|
|
res.Algorithm = FtpHashAlgorithm.MD5;
|
|
} else if (HasFeature(FtpCapability.XSHA1)) {
|
|
res.Value = GetXSHA1(path);
|
|
res.Algorithm = FtpHashAlgorithm.SHA1;
|
|
} else if (HasFeature(FtpCapability.XSHA256)) {
|
|
res.Value = GetXSHA256(path);
|
|
res.Algorithm = FtpHashAlgorithm.SHA256;
|
|
} else if (HasFeature(FtpCapability.XSHA512)) {
|
|
res.Value = GetXSHA512(path);
|
|
res.Algorithm = FtpHashAlgorithm.SHA512;
|
|
} else if (HasFeature(FtpCapability.XCRC)) {
|
|
res.Value = GetXCRC(path);
|
|
res.Algorithm = FtpHashAlgorithm.CRC;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
#if !CORE
|
|
delegate FtpHash AsyncGetChecksum(string path);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a checksum of the given file using a checksum method that the server supports, if any.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The algorithm used goes in this order:
|
|
/// 1. HASH command; server preferred algorithm. See <see cref="FtpClient.SetHashAlgorithm"/>
|
|
/// 2. MD5 / XMD5 commands
|
|
/// 3. XSHA1 command
|
|
/// 4. XSHA256 command
|
|
/// 5. XSHA512 command
|
|
/// 6. XCRC command
|
|
/// </remarks>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetChecksum(string path, AsyncCallback callback,
|
|
object state) {
|
|
AsyncGetChecksum func = new AsyncGetChecksum(GetChecksum);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state);
|
|
;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetChecksum"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetChecksum"/></param>
|
|
/// <returns><see cref="FtpHash"/> object containing the value and algorithm. Use the <see cref="FtpHash.IsValid"/> property to
|
|
/// determine if this command was successful. <see cref="FtpCommandException"/>s can be thrown from
|
|
/// the underlying calls.</returns>
|
|
public FtpHash EndGetChecksum(IAsyncResult ar) {
|
|
AsyncGetChecksum func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetChecksum)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Retrieves a checksum of the given file using a checksum method that the server supports, if any.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The algorithm used goes in this order:
|
|
/// 1. HASH command; server preferred algorithm. See <see cref="FtpClient.SetHashAlgorithm"/>
|
|
/// 2. MD5 / XMD5 commands
|
|
/// 3. XSHA1 command
|
|
/// 4. XSHA256 command
|
|
/// 5. XSHA512 command
|
|
/// 6. XCRC command
|
|
/// </remarks>
|
|
/// <param name="path">Full or relative path of the file to checksum</param>
|
|
/// <returns><see cref="FtpHash"/> object containing the value and algorithm. Use the <see cref="FtpHash.IsValid"/> property to
|
|
/// determine if this command was successful. <see cref="FtpCommandException"/>s can be thrown from
|
|
/// the underlying calls.</returns>
|
|
/// <example><code source="..\Examples\GetChecksum.cs" lang="cs" /></example>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<FtpHash> GetChecksumAsync(string path) {
|
|
//TODO: Add cancellation support
|
|
if (HasFeature(FtpCapability.HASH))
|
|
{
|
|
return await GetHashAsync(path);
|
|
}
|
|
else
|
|
{
|
|
FtpHash res = new FtpHash();
|
|
|
|
if (HasFeature(FtpCapability.MD5))
|
|
{
|
|
res.Value = await GetMD5Async(path);
|
|
res.Algorithm = FtpHashAlgorithm.MD5;
|
|
}
|
|
else if (HasFeature(FtpCapability.XMD5))
|
|
{
|
|
res.Value = await GetXMD5Async(path);
|
|
res.Algorithm = FtpHashAlgorithm.MD5;
|
|
}
|
|
else if (HasFeature(FtpCapability.XSHA1))
|
|
{
|
|
res.Value = await GetXSHA1Async(path);
|
|
res.Algorithm = FtpHashAlgorithm.SHA1;
|
|
}
|
|
else if (HasFeature(FtpCapability.XSHA256))
|
|
{
|
|
res.Value = await GetXSHA256Async(path);
|
|
res.Algorithm = FtpHashAlgorithm.SHA256;
|
|
}
|
|
else if (HasFeature(FtpCapability.XSHA512))
|
|
{
|
|
res.Value = await GetXSHA512Async(path);
|
|
res.Algorithm = FtpHashAlgorithm.SHA512;
|
|
}
|
|
else if (HasFeature(FtpCapability.XCRC))
|
|
{
|
|
res.Value = await GetXCRCAsync(path);
|
|
res.Algorithm = FtpHashAlgorithm.CRC;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region MD5
|
|
/// <summary>
|
|
/// Gets the MD5 hash of the specified file using MD5. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the MD5 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public string GetMD5(string path) {
|
|
// http://tools.ietf.org/html/draft-twine-ftpmd5-00#section-3.1
|
|
FtpReply reply;
|
|
string response;
|
|
|
|
if (!(reply = Execute("MD5 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
response = reply.Message;
|
|
if (response.StartsWith(path)) {
|
|
response = response.Remove(0, path.Length).Trim();
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string AsyncGetMD5(string path);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a MD5 hash. The MD5 command is non-standard
|
|
/// and not guaranteed to work.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetMD5(string path, AsyncCallback callback, object state) {
|
|
AsyncGetMD5 func = new AsyncGetMD5(GetMD5);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state);
|
|
;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetMD5"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetMD5"/></param>
|
|
/// <returns>The MD5 hash of the specified file.</returns>
|
|
public string EndGetMD5(IAsyncResult ar) {
|
|
AsyncGetMD5 func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetMD5)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the MD5 hash of the specified file using MD5 asynchronously. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the MD5 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<string> GetMD5Async(string path) {
|
|
FtpReply reply;
|
|
string response;
|
|
|
|
if (!(reply = await ExecuteAsync("MD5 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
response = reply.Message;
|
|
if (response.StartsWith(path))
|
|
{
|
|
response = response.Remove(0, path.Length).Trim();
|
|
}
|
|
|
|
return response;
|
|
}
|
|
#endif
|
|
#endregion
|
|
|
|
#region XCRC
|
|
|
|
/// <summary>
|
|
/// Get the CRC value of the specified file. This is a non-standard extension of the protocol
|
|
/// and may throw a FtpCommandException if the server does not support it.
|
|
/// </summary>
|
|
/// <param name="path">The path of the file you'd like the server to compute the CRC value for.</param>
|
|
/// <returns>The response from the server, typically the XCRC value. FtpCommandException thrown on error</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public string GetXCRC(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = Execute("XCRC " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string AsyncGetXCRC(string path);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a CRC hash. The XCRC command is non-standard
|
|
/// and not guaranteed to work.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetXCRC(string path, AsyncCallback callback, object state) {
|
|
AsyncGetXCRC func = new AsyncGetXCRC(GetXCRC);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state); ;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetXCRC"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetXCRC"/></param>
|
|
/// <returns>The CRC hash of the specified file.</returns>
|
|
public string EndGetXCRC(IAsyncResult ar) {
|
|
AsyncGetXCRC func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetXCRC)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the CRC hash of the specified file using XCRC asynchronously. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the CRC hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<string> GetXCRCAsync(string path) {
|
|
FtpReply reply;
|
|
string response;
|
|
|
|
if (!(reply = await ExecuteAsync("MD5 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
response = reply.Message;
|
|
if (response.StartsWith(path))
|
|
{
|
|
response = response.Remove(0, path.Length).Trim();
|
|
}
|
|
|
|
return response;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region XMD5
|
|
|
|
/// <summary>
|
|
/// Gets the MD5 hash of the specified file using XMD5. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the MD5 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public string GetXMD5(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = Execute("XMD5 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string AsyncGetXMD5(string path);
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a XMD5 hash. The XMD5 command is non-standard
|
|
/// and not guaranteed to work.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetXMD5(string path, AsyncCallback callback, object state) {
|
|
AsyncGetXMD5 func = new AsyncGetXMD5(GetXMD5);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state); ;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetXMD5"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetXMD5"/></param>
|
|
/// <returns>The MD5 hash of the specified file.</returns>
|
|
public string EndGetXMD5(IAsyncResult ar) {
|
|
AsyncGetXMD5 func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetXMD5)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the MD5 hash of the specified file using XMD5 asynchronously. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the MD5 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<string> GetXMD5Async(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = await ExecuteAsync("XMD5 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region XSHA1
|
|
|
|
/// <summary>
|
|
/// Gets the SHA-1 hash of the specified file using XSHA1. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the SHA-1 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public string GetXSHA1(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = Execute("XSHA1 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string AsyncGetXSHA1(string path);
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a SHA1 hash. The XSHA1 command is non-standard
|
|
/// and not guaranteed to work.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetXSHA1(string path, AsyncCallback callback, object state) {
|
|
AsyncGetXSHA1 func = new AsyncGetXSHA1(GetXSHA1);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state); ;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetXSHA1"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetXSHA1"/></param>
|
|
/// <returns>The SHA-1 hash of the specified file.</returns>
|
|
public string EndGetXSHA1(IAsyncResult ar) {
|
|
AsyncGetXSHA1 func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetXSHA1)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the SHA-1 hash of the specified file using XSHA1 asynchronously. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the SHA-1 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<string> GetXSHA1Async(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = await ExecuteAsync("XSHA1 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region XSHA256
|
|
|
|
/// <summary>
|
|
/// Gets the SHA-256 hash of the specified file using XSHA256. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the SHA-256 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public string GetXSHA256(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = Execute("XSHA256 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string AsyncGetXSHA256(string path);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a SHA256 hash. The XSHA256 command is non-standard
|
|
/// and not guaranteed to work.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetXSHA256(string path, AsyncCallback callback, object state) {
|
|
AsyncGetXSHA256 func = new AsyncGetXSHA256(GetXSHA256);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state); ;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetXSHA256"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetXSHA256"/></param>
|
|
/// <returns>The SHA-256 hash of the specified file.</returns>
|
|
public string EndGetXSHA256(IAsyncResult ar) {
|
|
AsyncGetXSHA256 func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetXSHA256)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the SHA-256 hash of the specified file using XSHA256 asynchronously. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the SHA-256 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<string> GetXSHA256Async(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = await ExecuteAsync("XSHA256 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region XSHA512
|
|
|
|
/// <summary>
|
|
/// Gets the SHA-512 hash of the specified file using XSHA512. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the SHA-512 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public string GetXSHA512(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = Execute("XSHA512 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
}
|
|
|
|
#if !CORE
|
|
delegate string AsyncGetXSHA512(string path);
|
|
|
|
/// <summary>
|
|
/// Begins an asynchronous operation to retrieve a SHA512 hash. The XSHA512 command is non-standard
|
|
/// and not guaranteed to work.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <param name="callback">AsyncCallback</param>
|
|
/// <param name="state">State Object</param>
|
|
/// <returns>IAsyncResult</returns>
|
|
public IAsyncResult BeginGetXSHA512(string path, AsyncCallback callback, object state) {
|
|
AsyncGetXSHA512 func = new AsyncGetXSHA512(GetXSHA512);
|
|
IAsyncResult ar = func.BeginInvoke(path, callback, state); ;
|
|
|
|
lock (m_asyncmethods) {
|
|
m_asyncmethods.Add(ar, func);
|
|
}
|
|
|
|
return ar;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends an asynchronous call to <see cref="BeginGetXSHA512"/>
|
|
/// </summary>
|
|
/// <param name="ar">IAsyncResult returned from <see cref="BeginGetXSHA512"/></param>
|
|
/// <returns>The SHA-512 hash of the specified file.</returns>
|
|
public string EndGetXSHA512(IAsyncResult ar) {
|
|
AsyncGetXSHA512 func = null;
|
|
|
|
lock (m_asyncmethods) {
|
|
if (!m_asyncmethods.ContainsKey(ar))
|
|
throw new InvalidOperationException("The specified IAsyncResult was not found in the collection.");
|
|
|
|
func = (AsyncGetXSHA512)m_asyncmethods[ar];
|
|
m_asyncmethods.Remove(ar);
|
|
}
|
|
|
|
return func.EndInvoke(ar);
|
|
}
|
|
|
|
#endif
|
|
#if ASYNC
|
|
/// <summary>
|
|
/// Gets the SHA-512 hash of the specified file using XSHA512 asynchronously. This is a non-standard extension
|
|
/// to the protocol and may or may not work. A FtpCommandException will be
|
|
/// thrown if the command fails.
|
|
/// </summary>
|
|
/// <param name="path">Full or relative path to remote file</param>
|
|
/// <returns>Server response, presumably the SHA-512 hash.</returns>
|
|
/// <exception cref="FtpCommandException">The command fails</exception>
|
|
public async Task<string> GetXSHA512Async(string path) {
|
|
FtpReply reply;
|
|
|
|
if (!(reply = await ExecuteAsync("XSHA512 " + path)).Success)
|
|
throw new FtpCommandException(reply);
|
|
|
|
return reply.Message;
|
|
|
|
}
|
|
#endif
|
|
#endregion
|
|
|
|
}
|
|
} |