Files
AutoSeller/FluentFTP/Stream/FtpSocketStream.cs
2018-01-02 01:12:03 +09:00

1158 lines
37 KiB
C#

using System;
using System.IO;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
#if CORE || NET45
using System.Threading.Tasks;
#endif
namespace FluentFTP {
/// <summary>
/// Stream class used for talking. Used by FtpClient, extended by FtpDataStream
/// </summary>
public class FtpSocketStream : Stream, IDisposable {
private SslProtocols m_SslProtocols;
public FtpSocketStream(SslProtocols defaultSslProtocols) {
m_SslProtocols = defaultSslProtocols;
}
/// <summary>
/// Used for tacking read/write activity on the socket
/// to determine if Poll() should be used to test for
/// socket connectivity. The socket in this class will
/// not know it has been disconnected if the remote host
/// closes the connection first. Using Poll() avoids
/// the exception that would be thrown when trying to
/// read or write to the disconnected socket.
/// </summary>
private DateTime m_lastActivity = DateTime.Now;
private Socket m_socket = null;
/// <summary>
/// The socket used for talking
/// </summary>
protected Socket Socket {
get {
return m_socket;
}
private set {
m_socket = value;
}
}
int m_socketPollInterval = 15000;
/// <summary>
/// Gets or sets the length of time in milliseconds
/// that must pass since the last socket activity
/// before calling Poll() on the socket to test for
/// connectivity. Setting this interval too low will
/// have a negative impact on performance. Setting this
/// interval to 0 disables Poll()'ing all together.
/// The default value is 15 seconds.
/// </summary>
public int SocketPollInterval {
get { return m_socketPollInterval; }
set { m_socketPollInterval = value; }
}
/// <summary>
/// Gets the number of available bytes on the socket, 0 if the
/// socket has not been initialized. This property is used internally
/// by FtpClient in an effort to detect disconnections and gracefully
/// reconnect the control connection.
/// </summary>
internal int SocketDataAvailable {
get {
if (m_socket != null)
return m_socket.Available;
return 0;
}
}
/// <summary>
/// Gets a value indicating if this socket stream is connected
/// </summary>
public bool IsConnected {
get {
try {
if (m_socket == null)
return false;
if (!m_socket.Connected) {
Close();
return false;
}
if (!CanRead || !CanWrite) {
Close();
return false;
}
if (m_socketPollInterval > 0 && DateTime.Now.Subtract(m_lastActivity).TotalMilliseconds > m_socketPollInterval) {
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Testing connectivity using Socket.Poll()...");
if (m_socket.Poll(500000, SelectMode.SelectRead) && m_socket.Available == 0) {
Close();
return false;
}
}
} catch (SocketException sockex) {
Close();
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpSocketStream.IsConnected: Caught and discarded SocketException while testing for connectivity: " + sockex.ToString());
return false;
} catch (IOException ioex) {
Close();
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpSocketStream.IsConnected: Caught and discarded IOException while testing for connectivity: " + ioex.ToString());
return false;
}
return true;
}
}
/// <summary>
/// Gets a value indicating if encryption is being used
/// </summary>
public bool IsEncrypted {
get {
#if NO_SSL
return false;
#else
return m_sslStream != null;
#endif
}
}
NetworkStream m_netStream = null;
/// <summary>
/// The non-encrypted stream
/// </summary>
private NetworkStream NetworkStream {
get {
return m_netStream;
}
set {
m_netStream = value;
}
}
#if !NO_SSL
SslStream m_sslStream = null;
/// <summary>
/// The encrypted stream
/// </summary>
private SslStream SslStream {
get {
return m_sslStream;
}
set {
m_sslStream = value;
}
}
#endif
/// <summary>
/// Gets the underlying stream, could be a NetworkStream or SslStream
/// </summary>
protected Stream BaseStream {
get {
#if NO_SSL
if (m_netStream != null)
return m_netStream;
#else
if (m_sslStream != null)
return m_sslStream;
else if (m_netStream != null)
return m_netStream;
#endif
return null;
}
}
/// <summary>
/// Gets a value indicating if this stream can be read
/// </summary>
public override bool CanRead {
get {
if (m_netStream != null)
return m_netStream.CanRead;
return false;
}
}
/// <summary>
/// Gets a value indicating if this stream if seekable
/// </summary>
public override bool CanSeek {
get {
return false;
}
}
/// <summary>
/// Gets a value indicating if this stream can be written to
/// </summary>
public override bool CanWrite {
get {
if (m_netStream != null)
return m_netStream.CanWrite;
return false;
}
}
/// <summary>
/// Gets the length of the stream
/// </summary>
public override long Length {
get {
return 0;
}
}
/// <summary>
/// Gets the current position of the stream. Trying to
/// set this property throws an InvalidOperationException()
/// </summary>
public override long Position {
get {
if (BaseStream != null)
return BaseStream.Position;
return 0;
}
set {
throw new InvalidOperationException();
}
}
event FtpSocketStreamSslValidation m_sslvalidate = null;
/// <summary>
/// Event is fired when a SSL certificate needs to be validated
/// </summary>
public event FtpSocketStreamSslValidation ValidateCertificate {
add {
m_sslvalidate += value;
}
remove {
m_sslvalidate -= value;
}
}
int m_readTimeout = Timeout.Infinite;
/// <summary>
/// Gets or sets the amount of time to wait for a read operation to complete. Default
/// value is Timeout.Infinite.
/// </summary>
public override int ReadTimeout {
get {
return m_readTimeout;
}
set {
m_readTimeout = value;
}
}
int m_connectTimeout = 30000;
/// <summary>
/// Gets or sets the length of time milliseconds to wait
/// for a connection succeed before giving up. The default
/// is 30000 (30 seconds).
/// </summary>
public int ConnectTimeout {
get {
return m_connectTimeout;
}
set {
m_connectTimeout = value;
}
}
/// <summary>
/// Gets the local end point of the socket
/// </summary>
public IPEndPoint LocalEndPoint {
get {
if (m_socket == null)
return null;
return (IPEndPoint)m_socket.LocalEndPoint;
}
}
/// <summary>
/// Gets the remote end point of the socket
/// </summary>
public IPEndPoint RemoteEndPoint {
get {
if (m_socket == null)
return null;
return (IPEndPoint)m_socket.RemoteEndPoint;
}
}
/// <summary>
/// Fires the SSL certificate validation event
/// </summary>
/// <param name="certificate">Certificate being validated</param>
/// <param name="chain">Certificate chain</param>
/// <param name="errors">Policy errors if any</param>
/// <returns>True if it was accepted, false otherwise</returns>
protected bool OnValidateCertificate(X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) {
FtpSocketStreamSslValidation evt = m_sslvalidate;
if (evt != null) {
FtpSslValidationEventArgs e = new FtpSslValidationEventArgs() {
Certificate = certificate,
Chain = chain,
PolicyErrors = errors,
Accept = (errors == SslPolicyErrors.None)
};
evt(this, e);
return e.Accept;
}
// if the event was not handled then only accept
// the certificate if there were no validation errors
return (errors == SslPolicyErrors.None);
}
/// <summary>
/// Throws an InvalidOperationException
/// </summary>
/// <param name="offset">Ignored</param>
/// <param name="origin">Ignored</param>
/// <returns></returns>
public override long Seek(long offset, SeekOrigin origin) {
throw new InvalidOperationException();
}
/// <summary>
/// Throws an InvalidOperationException
/// </summary>
/// <param name="value">Ignored</param>
public override void SetLength(long value) {
throw new InvalidOperationException();
}
/// <summary>
/// Flushes the stream
/// </summary>
public override void Flush() {
if (!IsConnected)
throw new InvalidOperationException("The FtpSocketStream object is not connected.");
if (BaseStream == null)
throw new InvalidOperationException("The base stream of the FtpSocketStream object is null.");
BaseStream.Flush();
}
#if ASYNC
/// <summary>
/// Flushes the stream asynchronously
/// </summary>
/// <param name="token">The <see cref="CancellationToken"/> for this task</param>
public override async Task FlushAsync(CancellationToken token) {
if (!IsConnected)
throw new InvalidOperationException("The FtpSocketStream object is not connected.");
if (BaseStream == null)
throw new InvalidOperationException("The base stream of the FtpSocketStream object is null.");
await BaseStream.FlushAsync(token);
}
#endif
/// <summary>
/// Bypass the stream and read directly off the socket.
/// </summary>
/// <param name="buffer">The buffer to read into</param>
/// <returns>The number of bytes read</returns>
internal int RawSocketRead(byte[] buffer) {
int read = 0;
if (m_socket != null && m_socket.Connected) {
read = m_socket.Receive(buffer, buffer.Length, 0);
}
return read;
}
#if NET45
/// <summary>
/// Bypass the stream and read directly off the socket.
/// </summary>
/// <param name="buffer">The buffer to read into</param>
/// <returns>The number of bytes read</returns>
internal async Task<int> RawSocketReadAsync(byte[] buffer)
{
int read = 0;
if (m_socket != null && m_socket.Connected)
{
var asyncResult = m_socket.BeginReceive(buffer, 0, buffer.Length, 0, null, null);
read = await Task.Factory.FromAsync(asyncResult, m_socket.EndReceive);
}
return read;
}
#endif
#if ASYNC && !NET45
/// <summary>
/// Bypass the stream and read directly off the socket.
/// </summary>
/// <param name="buffer">The buffer to read into</param>
/// <returns>The number of bytes read</returns>
internal async Task<int> RawSocketReadAsync(byte[] buffer)
{
int read = 0;
if (m_socket != null && m_socket.Connected)
{
read = await m_socket.ReceiveAsync(new ArraySegment<byte>(buffer), 0);
}
return read;
}
#endif
/// <summary>
/// Reads data from the stream
/// </summary>
/// <param name="buffer">Buffer to read into</param>
/// <param name="offset">Where in the buffer to start</param>
/// <param name="count">Number of bytes to be read</param>
/// <returns>The amount of bytes read from the stream</returns>
public override int Read(byte[] buffer, int offset, int count) {
#if !CORE
IAsyncResult ar = null;
#endif
if (BaseStream == null)
return 0;
m_lastActivity = DateTime.Now;
#if CORE
return BaseStream.ReadAsync(buffer, offset, count).Result;
#else
ar = BaseStream.BeginRead(buffer, offset, count, null, null);
if (!ar.AsyncWaitHandle.WaitOne(m_readTimeout, true)) {
Close();
throw new TimeoutException("Timed out trying to read data from the socket stream!");
}
return BaseStream.EndRead(ar);
#endif
}
#if ASYNC
/// <summary>
/// Reads data from the stream
/// </summary>
/// <param name="buffer">Buffer to read into</param>
/// <param name="offset">Where in the buffer to start</param>
/// <param name="count">Number of bytes to be read</param>
/// <param name="token">The <see cref="CancellationToken"/> for this task</param>
/// <returns>The amount of bytes read from the stream</returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) {
if (BaseStream == null)
return 0;
m_lastActivity = DateTime.Now;
return await BaseStream.ReadAsync(buffer, offset, count, token);
}
#endif
/// <summary>
/// Reads a line from the socket
/// </summary>
/// <param name="encoding">The type of encoding used to convert from byte[] to string</param>
/// <returns>A line from the stream, null if there is nothing to read</returns>
public string ReadLine(System.Text.Encoding encoding) {
List<byte> data = new List<byte>();
byte[] buf = new byte[1];
string line = null;
while (Read(buf, 0, buf.Length) > 0) {
data.Add(buf[0]);
if ((char)buf[0] == '\n') {
line = encoding.GetString(data.ToArray()).Trim('\r', '\n');
break;
}
}
return line;
}
/// <summary>
/// Reads all line from the socket
/// </summary>
/// <param name="encoding">The type of encoding used to convert from byte[] to string</param>
/// <param name="bufferSize">The size of the buffer</param>
/// <returns>A list of lines from the stream</returns>
public IEnumerable<string> ReadAllLines(System.Text.Encoding encoding, int bufferSize) {
int charRead;
List<byte> data = new List<byte>();
byte[] buf = new byte[bufferSize];
while ((charRead = Read(buf, 0, buf.Length)) > 0) {
var firstByteToReadIdx = 0;
var separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed
while (separatorIdx >= 0) // at least one '\n' returned
{
while (firstByteToReadIdx <= separatorIdx)
data.Add(buf[firstByteToReadIdx++]);
var line = encoding.GetString(data.ToArray()).Trim('\r', '\n'); // convert data to string
yield return line;
data.Clear();
separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed
}
while (firstByteToReadIdx < charRead) // add all remainings characters to data
data.Add(buf[firstByteToReadIdx++]);
}
}
#if ASYNC
/// <summary>
/// Reads a line from the socket asynchronously
/// </summary>
/// <param name="encoding">The type of encoding used to convert from byte[] to string</param>
/// <param name="token">The <see cref="CancellationToken"/> for this task</param>
/// <returns>A line from the stream, null if there is nothing to read</returns>
public async Task<string> ReadLineAsync(System.Text.Encoding encoding, CancellationToken token) {
List<byte> data = new List<byte>();
byte[] buf = new byte[1];
string line = null;
while (await ReadAsync(buf, 0, buf.Length, token) > 0) {
data.Add(buf[0]);
if ((char)buf[0] == '\n') {
line = encoding.GetString(data.ToArray()).Trim('\r', '\n');
break;
}
}
return line;
}
/// <summary>
/// Reads a line from the socket asynchronously
/// </summary>
/// <param name="encoding">The type of encoding used to convert from byte[] to string</param>
/// <returns>A line from the stream, null if there is nothing to read</returns>
public async Task<string> ReadLineAsync(System.Text.Encoding encoding) {
return await ReadLineAsync(encoding, CancellationToken.None);
}
/// <summary>
/// Reads all line from the socket
/// </summary>
/// <param name="encoding">The type of encoding used to convert from byte[] to string</param>
/// <param name="bufferSize">The size of the buffer</param>
/// <returns>A list of lines from the stream</returns>
public async Task<IEnumerable<string>> ReadAllLinesAsync(System.Text.Encoding encoding, int bufferSize)
{
int charRead;
List<byte> data = new List<byte>();
List<string> lines = new List<string>();
byte[] buf = new byte[bufferSize];
while ((charRead = await ReadAsync(buf, 0, buf.Length)) > 0)
{
var firstByteToReadIdx = 0;
var separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed
while (separatorIdx >= 0) // at least one '\n' returned
{
while (firstByteToReadIdx <= separatorIdx)
data.Add(buf[firstByteToReadIdx++]);
var line = encoding.GetString(data.ToArray()).Trim('\r', '\n'); // convert data to string
lines.Add(line);
data.Clear();
separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed
}
while (firstByteToReadIdx < charRead) // add all remainings characters to data
data.Add(buf[firstByteToReadIdx++]);
}
return lines;
}
#endif
/// <summary>
/// Writes data to the stream
/// </summary>
/// <param name="buffer">Buffer to write to stream</param>
/// <param name="offset">Where in the buffer to start</param>
/// <param name="count">Number of bytes to be read</param>
public override void Write(byte[] buffer, int offset, int count) {
if (BaseStream == null)
return;
BaseStream.Write(buffer, offset, count);
m_lastActivity = DateTime.Now;
}
#if ASYNC
/// <summary>
/// Writes data to the stream asynchronously
/// </summary>
/// <param name="buffer">Buffer to write to stream</param>
/// <param name="offset">Where in the buffer to start</param>
/// <param name="count">Number of bytes to be read</param>
/// <param name="token">The <see cref="CancellationToken"/> for this task</param>
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) {
if (BaseStream == null)
return;
await BaseStream.WriteAsync(buffer, offset, count, token);
m_lastActivity = DateTime.Now;
}
#endif
/// <summary>
/// Writes a line to the stream using the specified encoding
/// </summary>
/// <param name="encoding">Encoding used for writing the line</param>
/// <param name="buf">The data to write</param>
public void WriteLine(System.Text.Encoding encoding, string buf) {
byte[] data;
data = encoding.GetBytes((buf + "\r\n"));
Write(data, 0, data.Length);
}
#if ASYNC
/// <summary>
/// Writes a line to the stream using the specified encoding asynchronously
/// </summary>
/// <param name="encoding">Encoding used for writing the line</param>
/// <param name="buf">The data to write</param>
/// <param name="token">The <see cref="CancellationToken"/> for this task</param>
public async Task WriteLineAsync(System.Text.Encoding encoding, string buf, CancellationToken token) {
byte[] data = encoding.GetBytes(buf + "\r\n");
await WriteAsync(data, 0, data.Length, token);
}
/// <summary>
/// Writes a line to the stream using the specified encoding asynchronously
/// </summary>
/// <param name="encoding">Encoding used for writing the line</param>
/// <param name="buf">The data to write</param>
public async Task WriteLineAsync(System.Text.Encoding encoding, string buf) {
await WriteLineAsync(encoding, buf, CancellationToken.None);
}
#endif
#if CORE
/// <summary>
/// Disconnects from server
/// </summary>
public void Close()
{
Dispose(true);
}
#endif
/// <summary>
/// Disconnects from server
/// </summary>
protected override void Dispose(bool disposing) {
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Disposing FtpSocketStream...");
if (m_socket != null) {
try {
if (m_socket.Connected) {
////
// Calling Shutdown() with mono causes an
// exception if the remote host closed first
//m_socket.Shutdown(SocketShutdown.Both);
#if CORE
m_socket.Dispose();
#else
m_socket.Close();
#endif
}
#if !NET20 && !NET35
m_socket.Dispose();
#endif
} catch (SocketException ex) {
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded a SocketException while cleaning up the Socket: " + ex.ToString());
} finally {
m_socket = null;
}
}
if (m_netStream != null) {
try {
m_netStream.Dispose();
} catch (IOException ex) {
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded an IOException while cleaning up the NetworkStream: " + ex.ToString());
} finally {
m_netStream = null;
}
}
#if !NO_SSL
if (m_sslStream != null) {
try {
m_sslStream.Dispose();
} catch (IOException ex) {
FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded an IOException while cleaning up the SslStream: " + ex.ToString());
} finally {
m_sslStream = null;
}
}
#endif
}
/// <summary>
/// Sets socket options on the underlying socket
/// </summary>
/// <param name="level">SocketOptionLevel</param>
/// <param name="name">SocketOptionName</param>
/// <param name="value">SocketOptionValue</param>
public void SetSocketOption(SocketOptionLevel level, SocketOptionName name, bool value) {
if (m_socket == null)
throw new InvalidOperationException("The underlying socket is null. Have you established a connection?");
m_socket.SetSocketOption(level, name, value);
}
/// <summary>
/// Connect to the specified host
/// </summary>
/// <param name="host">The host to connect to</param>
/// <param name="port">The port to connect to</param>
/// <param name="ipVersions">Internet Protocol versions to support during the connection phase</param>
public void Connect(string host, int port, FtpIpVersion ipVersions) {
#if CORE
IPAddress[] addresses = Dns.GetHostAddressesAsync(host).Result;
#else
IAsyncResult ar = null;
IPAddress[] addresses = Dns.GetHostAddresses(host);
#endif
if (ipVersions == 0)
throw new ArgumentException("The ipVersions parameter must contain at least 1 flag.");
for (int i = 0; i < addresses.Length; i++) {
// we don't need to do this check unless
// a particular version of IP has been
// omitted so we won't.
if (ipVersions != FtpIpVersion.ANY) {
switch (addresses[i].AddressFamily) {
case AddressFamily.InterNetwork:
if ((ipVersions & FtpIpVersion.IPv4) != FtpIpVersion.IPv4) {
#if DEBUG
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV4 address : " + addresses[i].ToString());
#endif
continue;
}
break;
case AddressFamily.InterNetworkV6:
if ((ipVersions & FtpIpVersion.IPv6) != FtpIpVersion.IPv6) {
#if DEBUG
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV6 address : " + addresses[i].ToString());
#endif
continue;
}
break;
}
}
if (FtpTrace.LogIP) {
FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to " + addresses[i].ToString() + ":" + port);
} else {
FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to ***:" + port);
}
m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp);
#if CORE
m_socket.ConnectAsync(addresses[i], port).Wait();
break;
#else
ar = m_socket.BeginConnect(addresses[i], port, null, null);
if (!ar.AsyncWaitHandle.WaitOne(m_connectTimeout, true)) {
Close();
// check to see if we're out of addresses, and throw a TimeoutException
if ((i + 1) == addresses.Length) {
throw new TimeoutException("Timed out trying to connect!");
}
} else {
m_socket.EndConnect(ar);
// we got a connection, break out
// of the loop.
break;
}
#endif
}
// make sure that we actually connected to
// one of the addresses returned from GetHostAddresses()
if (m_socket == null || !m_socket.Connected) {
Close();
throw new IOException("Failed to connect to host.");
}
m_netStream = new NetworkStream(m_socket);
m_lastActivity = DateTime.Now;
}
#if ASYNC
/// <summary>
/// Connect to the specified host
/// </summary>
/// <param name="host">The host to connect to</param>
/// <param name="port">The port to connect to</param>
/// <param name="ipVersions">Internet Protocol versions to support during the connection phase</param>
public async Task ConnectAsync(string host, int port, FtpIpVersion ipVersions)
{
IPAddress[] addresses = await Dns.GetHostAddressesAsync(host);
if (ipVersions == 0)
throw new ArgumentException("The ipVersions parameter must contain at least 1 flag.");
for (int i = 0; i < addresses.Length; i++)
{
// we don't need to do this check unless
// a particular version of IP has been
// omitted so we won't.
if (ipVersions != FtpIpVersion.ANY)
{
switch (addresses[i].AddressFamily)
{
case AddressFamily.InterNetwork:
if ((ipVersions & FtpIpVersion.IPv4) != FtpIpVersion.IPv4)
{
#if DEBUG
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV4 address : " + addresses[i].ToString());
#endif
continue;
}
break;
case AddressFamily.InterNetworkV6:
if ((ipVersions & FtpIpVersion.IPv6) != FtpIpVersion.IPv6)
{
#if DEBUG
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV6 address : " + addresses[i].ToString());
#endif
continue;
}
break;
}
}
if (FtpTrace.LogIP)
{
FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to " + addresses[i].ToString() + ":" + port);
}
else
{
FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to ***:" + port);
}
m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp);
#if CORE
await m_socket.ConnectAsync(addresses[i], port);
break;
#else
var connectResult = m_socket.BeginConnect(addresses[i], port, null, null);
await Task.Factory.FromAsync(connectResult, m_socket.EndConnect);
break;
#endif
}
// make sure that we actually connected to
// one of the addresses returned from GetHostAddresses()
if (m_socket == null || !m_socket.Connected)
{
Close();
throw new IOException("Failed to connect to host.");
}
m_netStream = new NetworkStream(m_socket);
m_lastActivity = DateTime.Now;
}
#endif
#if !NO_SSL
/// <summary>
/// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event.
/// If this event is not handled and there are SslPolicyErrors present, the certificate will
/// not be accepted.
/// </summary>
/// <param name="targethost">The host to authenticate the certificate against</param>
public void ActivateEncryption(string targethost) {
ActivateEncryption(targethost, null, m_SslProtocols);
}
#if ASYNC
/// <summary>
/// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event.
/// If this event is not handled and there are SslPolicyErrors present, the certificate will
/// not be accepted.
/// </summary>
/// <param name="targethost">The host to authenticate the certificate against</param>
public async Task ActivateEncryptionAsync(string targethost)
{
await ActivateEncryptionAsync(targethost, null, m_SslProtocols);
}
#endif
/// <summary>
/// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event.
/// If this event is not handled and there are SslPolicyErrors present, the certificate will
/// not be accepted.
/// </summary>
/// <param name="targethost">The host to authenticate the certificate against</param>
/// <param name="clientCerts">A collection of client certificates to use when authenticating the SSL stream</param>
public void ActivateEncryption(string targethost, X509CertificateCollection clientCerts) {
ActivateEncryption(targethost, clientCerts, m_SslProtocols);
}
#if ASYNC
/// <summary>
/// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event.
/// If this event is not handled and there are SslPolicyErrors present, the certificate will
/// not be accepted.
/// </summary>
/// <param name="targethost">The host to authenticate the certificate against</param>
/// <param name="clientCerts">A collection of client certificates to use when authenticating the SSL stream</param>
public async Task ActivateEncryptionAsync(string targethost, X509CertificateCollection clientCerts)
{
await ActivateEncryptionAsync(targethost, clientCerts, m_SslProtocols);
}
#endif
/// <summary>
/// Activates SSL on this stream using the specified protocols. Fires the ValidateCertificate event.
/// If this event is not handled and there are SslPolicyErrors present, the certificate will
/// not be accepted.
/// </summary>
/// <param name="targethost">The host to authenticate the certificate against</param>
/// <param name="clientCerts">A collection of client certificates to use when authenticating the SSL stream</param>
/// <param name="sslProtocols">A bitwise parameter for supported encryption protocols.</param>
/// <exception cref="AuthenticationException">Thrown when authentication fails</exception>
public void ActivateEncryption(string targethost, X509CertificateCollection clientCerts, SslProtocols sslProtocols) {
if (!IsConnected)
throw new InvalidOperationException("The FtpSocketStream object is not connected.");
if (m_netStream == null)
throw new InvalidOperationException("The base network stream is null.");
if (m_sslStream != null)
throw new InvalidOperationException("SSL Encryption has already been enabled on this stream.");
try {
DateTime auth_start;
TimeSpan auth_time_total;
#if CORE
m_sslStream = new SslStream(NetworkStream, true, new RemoteCertificateValidationCallback(
delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
return OnValidateCertificate(certificate, chain, sslPolicyErrors);
}
));
#else
m_sslStream = new FtpSslStream(NetworkStream, true, new RemoteCertificateValidationCallback(
delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
return OnValidateCertificate(certificate, chain, sslPolicyErrors);
}
));
#endif
auth_start = DateTime.Now;
#if CORE
m_sslStream.AuthenticateAsClientAsync(targethost, clientCerts, sslProtocols, true).Wait();
#else
m_sslStream.AuthenticateAsClient(targethost, clientCerts, sslProtocols, true);
#endif
auth_time_total = DateTime.Now.Subtract(auth_start);
FtpTrace.WriteStatus(FtpTraceLevel.Info, "FTPS Authentication Successful");
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Time to activate encryption: " + auth_time_total.Hours + "h " + auth_time_total.Minutes + "m " + auth_time_total.Seconds + "s. Total Seconds: " + auth_time_total.TotalSeconds + ".");
} catch (AuthenticationException) {
// authentication failed and in addition it left our
// ssl stream in an unusable state so cleanup needs
// to be done and the exception can be re-thrown for
// handling down the chain. (Add logging?)
Close();
FtpTrace.WriteStatus(FtpTraceLevel.Error, "FTPS Authentication Failed");
throw;
}
}
#if ASYNC
/// <summary>
/// Activates SSL on this stream using the specified protocols. Fires the ValidateCertificate event.
/// If this event is not handled and there are SslPolicyErrors present, the certificate will
/// not be accepted.
/// </summary>
/// <param name="targethost">The host to authenticate the certificate against</param>
/// <param name="clientCerts">A collection of client certificates to use when authenticating the SSL stream</param>
/// <param name="sslProtocols">A bitwise parameter for supported encryption protocols.</param>
/// <exception cref="AuthenticationException">Thrown when authentication fails</exception>
public async Task ActivateEncryptionAsync(string targethost, X509CertificateCollection clientCerts, SslProtocols sslProtocols)
{
if (!IsConnected)
throw new InvalidOperationException("The FtpSocketStream object is not connected.");
if (m_netStream == null)
throw new InvalidOperationException("The base network stream is null.");
if (m_sslStream != null)
throw new InvalidOperationException("SSL Encryption has already been enabled on this stream.");
try
{
DateTime auth_start;
TimeSpan auth_time_total;
#if CORE
m_sslStream = new SslStream(NetworkStream, true, new RemoteCertificateValidationCallback(
delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
return OnValidateCertificate(certificate, chain, sslPolicyErrors);
}
));
#else
m_sslStream = new FtpSslStream(NetworkStream, true, new RemoteCertificateValidationCallback(
delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return OnValidateCertificate(certificate, chain, sslPolicyErrors);
}
));
#endif
auth_start = DateTime.Now;
await m_sslStream.AuthenticateAsClientAsync(targethost, clientCerts, sslProtocols, true);
auth_time_total = DateTime.Now.Subtract(auth_start);
FtpTrace.WriteStatus(FtpTraceLevel.Info, "FTPS Authentication Successful");
FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Time to activate encryption: " + auth_time_total.Hours + "h " + auth_time_total.Minutes + "m " + auth_time_total.Seconds + "s. Total Seconds: " + auth_time_total.TotalSeconds + ".");
}
catch (AuthenticationException)
{
// authentication failed and in addition it left our
// ssl stream in an unusable state so cleanup needs
// to be done and the exception can be re-thrown for
// handling down the chain. (Add logging?)
Close();
FtpTrace.WriteStatus(FtpTraceLevel.Error, "FTPS Authentication Failed");
throw;
}
}
#endif
#endif
#if !CORE
/// <summary>
/// Deactivates SSL on this stream using the specified protocols and reverts back to plain-text FTP.
/// </summary>
public void DeactivateEncryption() {
if (!IsConnected)
throw new InvalidOperationException("The FtpSocketStream object is not connected.");
if (m_sslStream == null)
throw new InvalidOperationException("SSL Encryption has not been enabled on this stream.");
m_sslStream.Close();
m_sslStream = null;
}
#endif
/// <summary>
/// Instructs this stream to listen for connections on the specified address and port
/// </summary>
/// <param name="address">The address to listen on</param>
/// <param name="port">The port to listen on</param>
public void Listen(IPAddress address, int port) {
if (!IsConnected) {
if (m_socket == null)
m_socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
m_socket.Bind(new IPEndPoint(address, port));
m_socket.Listen(1);
}
}
/// <summary>
/// Accepts a connection from a listening socket
/// </summary>
public void Accept() {
if (m_socket != null)
m_socket = m_socket.Accept();
}
#if NET45
/// <summary>
/// Accepts a connection from a listening socket
/// </summary>
public async Task AcceptAsync()
{
if (m_socket!=null)
{
var iar = m_socket.BeginAccept(null, null);
await Task.Factory.FromAsync(iar, m_socket.EndAccept);
}
}
#endif
#if ASYNC && !NET45
/// <summary>
/// Accepts a connection from a listening socket
/// </summary>
public async Task AcceptAsync() {
if (m_socket != null) {
m_socket = await m_socket.AcceptAsync();
#if CORE
m_netStream = new NetworkStream(m_socket);
#endif
}
}
#else
/// <summary>
/// Asynchronously accepts a connection from a listening socket
/// </summary>
/// <param name="callback"></param>
/// <param name="state"></param>
/// <returns></returns>
public IAsyncResult BeginAccept(AsyncCallback callback, object state) {
if (m_socket != null)
return m_socket.BeginAccept(callback, state);
return null;
}
/// <summary>
/// Completes a BeginAccept() operation
/// </summary>
/// <param name="ar">IAsyncResult returned from BeginAccept</param>
public void EndAccept(IAsyncResult ar) {
if (m_socket != null) {
m_socket = m_socket.EndAccept(ar);
m_netStream = new NetworkStream(m_socket);
}
}
#endif
}
}