//******************************************************************************************************
// FtpControlChannel.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 05/22/2003 - J. Ritchie Carroll
// Generated original version of source code.
// 08/06/2009 - Josh L. Patterson
// Edited Comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/17/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
#region [ Contributor License Agreements ]
//*******************************************************************************************************
//
// Code based on the following project:
// http://www.codeproject.com/KB/IP/net_ftp_upload.aspx
//
// Copyright Alex Kwok & Uwe Keim
//
// The Code Project Open License (CPOL):
// http://www.codeproject.com/info/cpol10.aspx
//
//*******************************************************************************************************
#endregion
using System;
using System.Collections;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
namespace GSF.Net.Ftp
{
#region [ Enumerations ]
///
/// FTP transfer mode enumeration.
///
public enum TransferMode
{
///
/// Transfer files in ASCII mode.
///
Ascii,
///
/// Transfer files in binary mode.
///
Binary,
///
/// File transfer mode is undetermined.
///
Unknown
}
#endregion
///
/// FTP control channel.
///
public class FtpControlChannel : IDisposable
{
#region [ Members ]
// Fields
private readonly FtpClient m_sessionHost;
private FtpSessionConnected m_session;
private TcpClient m_connection;
private string m_server;
private int m_port;
private TransferMode m_currentTransferMode;
private FtpResponse m_lastResponse;
private bool m_disposed;
#endregion
#region [ Constructors ]
internal FtpControlChannel(FtpClient host)
{
m_connection = new TcpClient();
m_server = "localhost";
m_port = 21;
m_sessionHost = host;
m_currentTransferMode = TransferMode.Unknown;
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~FtpControlChannel()
{
Dispose(false);
}
#endregion
#region [ Properties ]
internal string Server
{
get
{
return m_server;
}
set
{
if (value.Length == 0)
throw new ArgumentNullException("value", "Server property must not be blank.");
m_server = value;
}
}
internal int Port
{
get
{
return m_port;
}
set
{
m_port = value;
}
}
///
/// Last response from control channel.
///
public FtpResponse LastResponse
{
get
{
return m_lastResponse;
}
}
internal FtpSessionConnected Session
{
get
{
return m_session;
}
set
{
m_session = value;
}
}
#endregion
#region [ Methods ]
///
/// Releases all the resources used by the object.
///
public void Close()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void IDisposable.Dispose()
{
Close();
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
// This will be done regardless of whether the object is finalized or disposed.
if (disposing)
{
m_lastResponse = null;
if ((object)m_connection != null)
m_connection.Close();
m_connection = null;
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
}
}
}
///
/// Connects the .
///
public void Connect()
{
if ((object)m_connection == null)
return;
m_connection.Connect(m_server, m_port);
try
{
m_lastResponse = new FtpResponse(m_connection.GetStream());
if (m_lastResponse.Code != FtpResponse.ServiceReady)
throw new FtpServerDownException("FTP service unavailable.", m_lastResponse);
}
catch
{
Close();
throw;
}
}
///
/// Send FTP command to control channel.
///
/// A representing the command to send.
public void Command(string cmd)
{
if ((object)m_connection == null)
return;
byte[] buff = Encoding.Default.GetBytes(cmd + Environment.NewLine);
NetworkStream stream = m_connection.GetStream();
m_sessionHost.OnCommandSent(cmd);
stream.Write(buff, 0, buff.Length);
RefreshResponse();
}
///
/// Refresh response from control channel.
///
public void RefreshResponse()
{
if ((object)m_connection == null)
return;
lock (this)
{
m_lastResponse = new FtpResponse(m_connection.GetStream());
}
foreach (string s in m_lastResponse.Respones)
{
m_sessionHost.OnResponseReceived(s);
}
}
internal void REST(long offset)
{
Command("REST " + offset);
if (m_lastResponse.Code != FtpResponse.RequestFileActionPending)
throw new FtpResumeNotSupportedException(m_lastResponse);
}
internal void STOR(string name)
{
Type(TransferMode.Binary);
Command("STOR " + name);
if (m_lastResponse.Code != FtpResponse.DataChannelOpenedTransferStart && m_lastResponse.Code != FtpResponse.FileOkBeginOpenDataChannel)
throw new FtpCommandException("Failed to send file " + name + ".", m_lastResponse);
}
internal void RETR(string name)
{
Type(TransferMode.Binary);
Command("RETR " + name);
if (m_lastResponse.Code != FtpResponse.DataChannelOpenedTransferStart && m_lastResponse.Code != FtpResponse.FileOkBeginOpenDataChannel)
throw new FtpCommandException("Failed to retrieve file " + name + ".", m_lastResponse);
}
internal void DELE(string fileName)
{
Command("DELE " + fileName);
if (m_lastResponse.Code != FtpResponse.RequestFileActionComplete) // 250)
throw new FtpCommandException("Failed to delete file " + fileName + ".", m_lastResponse);
}
internal void RMD(string dirName)
{
Command("RMD " + dirName);
if (m_lastResponse.Code != FtpResponse.RequestFileActionComplete) // 250)
throw new FtpCommandException("Failed to remove subdirectory " + dirName + ".", m_lastResponse);
}
internal string PWD()
{
Command("PWD");
if (m_lastResponse.Code != 257)
throw new FtpCommandException("Cannot get current directory.", m_lastResponse);
Match m = s_pwdExpression.Match(m_lastResponse.Message);
return m.Groups[2].Value;
}
internal void CDUP()
{
Command("CDUP");
if (m_lastResponse.Code != FtpResponse.RequestFileActionComplete)
throw new FtpCommandException("Cannot move to parent directory (CDUP).", m_lastResponse);
}
internal void CWD(string path)
{
Command("CWD " + path);
if (m_lastResponse.Code != FtpResponse.RequestFileActionComplete && m_lastResponse.Code != FtpResponse.ClosingDataChannel)
throw new FtpCommandException("Cannot change directory to " + path + ".", m_lastResponse);
}
internal void QUIT()
{
Command("QUIT");
}
internal void Type(TransferMode mode)
{
if (mode == TransferMode.Unknown)
return;
if (mode == TransferMode.Ascii && m_currentTransferMode != TransferMode.Ascii)
Command("TYPE A");
else if (mode == TransferMode.Binary && m_currentTransferMode != TransferMode.Binary)
Command("TYPE I");
m_currentTransferMode = mode;
}
internal void Rename(string oldName, string newName)
{
Command("RNFR " + oldName);
if (m_lastResponse.Code != FtpResponse.RequestFileActionPending)
throw new FtpCommandException("Failed to rename file from " + oldName + " to " + newName + ".", m_lastResponse);
Command("RNTO " + newName);
if (m_lastResponse.Code != FtpResponse.RequestFileActionComplete)
throw new FtpCommandException("Failed to rename file from " + oldName + " to " + newName + ".", m_lastResponse);
}
internal Queue List(bool passive)
{
const string errorMsgListing = "Error when listing server directory.";
try
{
Type(TransferMode.Ascii);
FtpDataStream dataStream = GetPassiveDataStream();
Queue lineQueue = new Queue();
Command("LIST");
if (m_lastResponse.Code != FtpResponse.DataChannelOpenedTransferStart && m_lastResponse.Code != FtpResponse.FileOkBeginOpenDataChannel)
throw new FtpCommandException(errorMsgListing, m_lastResponse);
StreamReader lineReader = new StreamReader(dataStream, Encoding.Default);
string line = lineReader.ReadLine();
while ((object)line != null)
{
lineQueue.Enqueue(line);
line = lineReader.ReadLine();
}
lineReader.Close();
if (m_lastResponse.Code != FtpResponse.ClosingDataChannel)
throw new FtpCommandException(errorMsgListing, m_lastResponse);
return lineQueue;
}
catch (IOException ie)
{
throw new Exception(errorMsgListing, ie);
}
catch (SocketException se)
{
throw new Exception(errorMsgListing, se);
}
}
internal FtpDataStream GetPassiveDataStream()
{
return GetPassiveDataStream(TransferDirection.Download);
}
internal FtpDataStream GetPassiveDataStream(TransferDirection direction)
{
TcpClient client = new TcpClient();
int port = 0;
try
{
port = GetPassivePort();
client.Connect(m_server, port);
if (direction == TransferDirection.Download)
return new FtpInputDataStream(this, client);
else
return new FtpOutputDataStream(this, client);
}
catch (IOException ie)
{
throw new Exception("Failed to open passive port (" + port + ") data connection due to IO exception: " + ie.Message + ".", ie);
}
catch (SocketException se)
{
throw new Exception("Failed to open passive port (" + port + ") data connection due to socket exception: " + se.Message + ".", se);
}
}
private int GetPassivePort()
{
Command("PASV");
if (m_lastResponse.Code == FtpResponse.EnterPassiveMode)
{
string[] numbers = s_regularExpression.Match(m_lastResponse.Message).Groups[2].Value.Split(',');
return int.Parse(numbers[4]) * 256 + int.Parse(numbers[5]);
}
else
{
throw new FtpCommandException("Failed to enter passive mode.", m_lastResponse);
}
}
#endregion
#region [ Static ]
// Static Fields
private static readonly Regex s_regularExpression = new Regex("(\\()(.*)(\\))");
private static readonly Regex s_pwdExpression = new Regex("(\")(.*)(\")");
#endregion
}
}