Skip to content

Instantly share code, notes, and snippets.

@guange2015
Created April 1, 2014 06:23
Show Gist options
  • Save guange2015/9908726 to your computer and use it in GitHub Desktop.
Save guange2015/9908726 to your computer and use it in GitHub Desktop.
SharpFtpServer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading;
namespace Demo
{
class SharpFtpServer
{
TcpListener _listener;
public SharpFtpServer()
{
_listener = new TcpListener(IPAddress.Any, 21);
}
public void Start()
{
_listener.Start();
_listener.BeginAcceptTcpClient(AcceptClientHandle, _listener);
}
void AcceptClientHandle(IAsyncResult ar)
{
TcpClient client = _listener.EndAcceptTcpClient(ar);
_listener.BeginAcceptTcpClient(AcceptClientHandle, ar.AsyncState);
ClientConnection connectionClient = new ClientConnection(client);
ThreadPool.QueueUserWorkItem(connectionClient.HandleClient, client);
}
}
class ClientConnection
{
TcpClient _controlClient;
NetworkStream _controlStream;
StreamWriter _controlWriter;
StreamReader _controlReader;
TcpListener _passiveListener;
enum DataConnectionType { Active, Passive };
DataConnectionType _dataConnectionType = DataConnectionType.Passive;
string _currentDirectory = @"c:\prj";
public string _transferType { get; set; }
public ClientConnection(TcpClient client)
{
_controlClient = client;
_controlStream = _controlClient.GetStream();
_controlWriter = new StreamWriter(_controlStream, Encoding.ASCII);
_controlReader = new StreamReader(_controlStream, Encoding.ASCII);
}
public void HandleClient(object obj)
{
_controlWriter.WriteLine("220 Service Ready.");
_controlWriter.Flush();
string line;
while (!string.IsNullOrEmpty(line = _controlReader.ReadLine()))
{
string response = null;
string[] command = line.Split(' ');
string cmd = command[0].ToUpperInvariant();
string arguments = command.Length > 1 ? line.Substring(command[0].Length + 1) : null;
if (string.IsNullOrWhiteSpace(arguments)) arguments = null;
Console.WriteLine("{0} {1}", cmd, arguments);
if (response == null)
{
switch (cmd)
{
case "USER":
response = User(arguments);
break;
case "PASS":
response = Password(arguments);
break;
case "QUIT":
response = "221 Goodbye";
break;
case "PWD":
response = "257 \"/\" is current directory.";
break;
case "TYPE":
string[] splitArgs = arguments.Split(' ');
response = Type(splitArgs[0], splitArgs.Length > 1 ? splitArgs[1] : null);
break;
case "PASV":
response = Passive();
break;
case "LIST":
response = List(arguments);
break;
case "SIZE":
response = FileSize(arguments);
break;
case "RETR":
response = Retrieve(arguments);
break;
case "STOR":
response = Store(arguments);
break;
case "CWD":
response = "250 OK.";
break;
default:
response = "502 Command not implemented";
break;
}
}
Console.WriteLine(response);
_controlWriter.WriteLine(response);
_controlWriter.Flush();
if (response.StartsWith("221"))
{
break;
}
}
_controlClient.Close();
}
private int CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[4096];
int count = 0;
int total = 0;
while ((count = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, count);
total += count;
}
return total;
}
#region FTP commands
private string User(string arguments)
{
return "331 OK.";
}
private string Password(string arguments)
{
return "230 OK.";
}
private string Store(string pathname)
{
if (_dataConnectionType == DataConnectionType.Passive)
{
if (pathname == null)
{
pathname = string.Empty;
}
string path = NormalizeFilename(pathname);
_passiveListener.BeginAcceptTcpClient(DoStore, path);
return string.Format("150 Opening {0} mode data transfer for STOR", _dataConnectionType);
}
return "450 Requested file action not taken";
}
private void DoStore(IAsyncResult ar)
{
if (_dataConnectionType == DataConnectionType.Passive)
{
TcpClient dataClient = _passiveListener.EndAcceptTcpClient(ar);
string pathname = (string)ar.AsyncState;
using (FileStream fs = new FileStream(pathname, FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (NetworkStream dataStream = dataClient.GetStream())
{
CopyStream(dataStream, fs);
dataClient.Close();
dataClient = null;
_controlWriter.WriteLine("226 Closing data connection, file transfer successful");
_controlWriter.Flush();
}
}
}
private string Retrieve(string pathname)
{
if (_dataConnectionType == DataConnectionType.Passive)
{
if (pathname == null)
{
pathname = string.Empty;
}
string path = NormalizeFilename(pathname);
if (File.Exists(path))
{
_passiveListener.BeginAcceptTcpClient(DoRetrieve, path);
return string.Format("150 Opening {0} mode data transfer for RETR", _dataConnectionType);
}
}
return "450 Requested file action not taken";
}
private void DoRetrieve(IAsyncResult ar)
{
if (_dataConnectionType == DataConnectionType.Passive)
{
TcpClient dataClient = _passiveListener.EndAcceptTcpClient(ar);
string pathname = (string)ar.AsyncState;
using (FileStream fs = new FileStream(pathname, FileMode.Open, FileAccess.Read))
using (NetworkStream dataStream = dataClient.GetStream())
{
CopyStream(fs, dataStream);
dataClient.Close();
dataClient = null;
_controlWriter.WriteLine("226 Closing data connection, file transfer successful");
_controlWriter.Flush();
}
}
}
private string FileSize(string filename)
{
string filepath = NormalizeFilename(filename);
long filelen = 0;
if (File.Exists(filepath))
{
FileInfo info = new FileInfo(filepath);
filelen = info.Length;
}
return string.Format("213 {0}", filelen);
}
private string List(string pathname)
{
if (pathname == null)
{
pathname = string.Empty;
}
pathname = new DirectoryInfo(Path.Combine(_currentDirectory, pathname)).FullName;
if (Directory.Exists(pathname))
{
if (_dataConnectionType == DataConnectionType.Passive)
{
_passiveListener.BeginAcceptTcpClient(DoList, pathname);
}
return string.Format("150 Opening {0} mode data transfer for LIST", _dataConnectionType);
}
return "450 Requested file action not taken";
}
private void DoList(IAsyncResult result)
{
if (_dataConnectionType == DataConnectionType.Passive)
{
TcpClient dataClient = _passiveListener.EndAcceptTcpClient(result);
string pathname = (string)result.AsyncState;
using (NetworkStream dataStream = dataClient.GetStream())
{
StreamReader dataReader = new StreamReader(dataStream, Encoding.ASCII);
StreamWriter dataWriter = new StreamWriter(dataStream, Encoding.ASCII);
IEnumerable<string> directories = Directory.EnumerateFileSystemEntries(pathname);
foreach (string dir in directories)
{
DirectoryInfo d = new DirectoryInfo(dir);
string date = d.LastWriteTime < DateTime.Now - TimeSpan.FromDays(180) ?
d.LastWriteTime.ToString("MM dd yyyy") :
d.LastWriteTime.ToString("MM dd HH:mm");
string fullmode = "rwxrwxrwx";
string filesize = "4096";
if (!IsDir(dir))
{
fullmode = "-" + fullmode;
FileInfo info = new FileInfo(dir);
filesize = string.Format("{0}", info.Length);
}
else
{
fullmode = "d" + fullmode;
}
string line = string.Format("{3} 1 user group {0,8} {1} {2}", filesize, date, d.Name, fullmode);
dataWriter.WriteLine(line);
dataWriter.Flush();
}
dataClient.Close();
dataClient = null;
_controlWriter.WriteLine("226 Transfer complete");
_controlWriter.Flush();
}
}
}
private string Passive()
{
IPAddress localAress = ((IPEndPoint)_controlClient.Client.LocalEndPoint).Address;
_passiveListener = new TcpListener(localAress, 0);
_passiveListener.Start();
IPEndPoint localEndpoint = ((IPEndPoint)_passiveListener.LocalEndpoint);
byte[] address = localAress.GetAddressBytes();
short port = (short)localEndpoint.Port;
byte[] portArray = BitConverter.GetBytes(port);
if (BitConverter.IsLittleEndian)
Array.Reverse(portArray);
return string.Format("227 Entering Passive Mode ({0},{1},{2},{3},{4},{5})",
address[0], address[1], address[2], address[3], portArray[0], portArray[1]);
}
private string Type(string typeCode, string formatControl)
{
string response = "500 ERROR";
switch (typeCode)
{
case "A":
case "I":
response = "200 OK";
_transferType = typeCode;
break;
case "E":
case "L":
default:
response = "504 Command not implemented for that parameter.";
break;
}
if (formatControl != null)
{
switch (formatControl)
{
case "N":
response = "200 OK";
break;
case "T":
case "C":
default:
response = "504 Command not implemented for that parameter.";
break;
}
}
return response;
}
#endregion
private string NormalizeFilename(string path)
{
if (path == null)
{
path = string.Empty;
}
if (path == "/")
{
return _currentDirectory;
}
else if (path.StartsWith("/"))
{
path = new FileInfo(Path.Combine(_currentDirectory, path.Substring(1))).FullName;
}
else
{
path = new FileInfo(Path.Combine(_currentDirectory, path)).FullName;
}
return path;
}
public static bool IsDir(string filepath)
{
FileInfo fi = new FileInfo(filepath);
if ((fi.Attributes & FileAttributes.Directory) != 0)
return true;
else
{
return false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment