Files
VirtualFS/VirtualFS/Physical/SFtpFileSystem.cs
grzegorz.russek 02d95583e9 Usages cleanup
2024-05-15 09:49:31 +02:00

498 lines
16 KiB
C#

/*
* VirtualFS - Virtual File System library.
* Copyright (c) 2021, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using Renci.SshNet;
using Renci.SshNet.Sftp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using VirtualFS.Base;
namespace VirtualFS.Physical
{
public class SFtpFileSystem : BaseFileSystem
{
private string _ftpServer;
private string _userName;
private string _password;
private byte[] _key;
private byte[] _fingerPrint;
private string _rootPath;
private Action<Exception> _exHandler;
private SftpClient _client;
private SftpClient Client
{
get
{
if (_client == null)
_client = CreateClientPrivate();
return _client;
}
}
/// <summary>Initializes a new instance of the <see cref="SFtpFileSystem"/> class.</summary>
/// <param name="host">The SFTP server.</param>
/// <param name="userName">SFTP User name.</param>
/// <param name="password">SFTP password.</param>
/// <param name="key">SFTP private key.</param>
/// <param name="fingerPrint">SFTP server fingerprint.</param>
/// <param name="rootPath">SFTP root path on server.</param>
public SFtpFileSystem(string host, string userName, string password, byte[] key, byte[] fingerPrint, string rootPath, Action<Exception> exHandler = null)
: base(false)
{
_exHandler = exHandler ?? (e => { });
_ftpServer = host;
_userName = userName;
_password = password;
_key = key;
_fingerPrint = fingerPrint;
_rootPath = rootPath;
if (_ftpServer.EndsWith("/"))
_ftpServer = _ftpServer.Substring(0, _ftpServer.Length - 1);
if (!string.IsNullOrEmpty(_rootPath) && _rootPath.EndsWith("/"))
_rootPath = _rootPath.Substring(0, _rootPath.Length - 1);
if (_rootPath == "/")
_rootPath = string.Empty;
}
private SftpClient CreateClientPrivate()
{
var ams = new List<AuthenticationMethod>();
if (!string.IsNullOrEmpty(_password))
ams.Add(new PasswordAuthenticationMethod(_userName, _password));
if (_key != null && _key.Length > 0)
using (var ms = new MemoryStream(_key))
ams.Add(new PrivateKeyAuthenticationMethod(_userName, new PrivateKeyFile(ms)));
var ci = new ConnectionInfo(_ftpServer, _userName, ams.ToArray());
var client = new SftpClient(ci);
if (_fingerPrint != null && _fingerPrint.Length > 0)
client.HostKeyReceived += (sender, e) =>
{
e.CanTrust = true;
if (_fingerPrint.Length == e.FingerPrint.Length)
for (var i = 0; i < _fingerPrint.Length; i++)
if (_fingerPrint[i] != e.FingerPrint[i])
{
e.CanTrust = false;
break;
}
else
e.CanTrust = false;
};
else
client.HostKeyReceived += (sender, e) => e.CanTrust = true;
client.Connect();
return client;
}
private Entry CreateEntry(Path path, ISftpFile ls, bool pathIsParent = true)
{
Entry e = null;
if (ls.IsDirectory)
e = new Directory
{
IsReadOnly = IsReadOnly,
Path = pathIsParent ? (path + (ls.Name + "/")) : path,
FileSystem = this,
Data = ls,
};
else
e = new File
{
CreationTime = ls.LastWriteTime,
LastWriteTime = ls.LastWriteTime,
LastAccessTime = ls.LastAccessTime,
Size = ls.Length,
IsReadOnly = IsReadOnly,
Path = pathIsParent ? (path + ls.Name) : path,
FileSystem = this,
Data = ls,
};
return e;
}
private string AdaptPath(SftpClient c, Path path)
{
var r = path.ToString().Substring(1);
if (!string.IsNullOrEmpty(_rootPath)) {
r = (string)(_rootPath + (string)path);
if (string.IsNullOrEmpty(r))
return null;
r = r[0] == Path.SeparatorChar ? r.Substring(1) : r;
}
var wd = c.WorkingDirectory;
if (!wd.EndsWith(Path.SeparatorChar.ToString()))
wd += Path.SeparatorChar;
return wd + r;
}
/// <summary>Get path to physical file containing entry.</summary>
/// <param name="path">Virtual file system path.</param>
/// <returns>Real file system path or <c>null</c> if real
/// path doesn't exist.</returns>
/// <remarks>This may not work with all file systems. Implementation
/// should return null if real path can't be determined.</remarks>
public override string EntryRealPath(Path path)
{
string uri = _ftpServer;
if (!string.IsNullOrEmpty(_rootPath))
uri += _rootPath;
if (path.Parent != null)
uri += (string)path.Parent + (string)path.Name ?? string.Empty;
return uri;
}
/// <summary>Check if given path exists.</summary>
/// <param name="path">Path to check.</param>
/// <returns>Returns <c>true</c> if entry does
/// exist, otherwise <c>false</c>.</returns>
public override bool Exists(Path path)
{
try
{
return Client.Exists(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
return false;
}
}
/// <summary>Get entries located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <param name="mask">Mask to filter out unwanted entries.</param>
/// <returns>Returns entry information.</returns>
public override IEnumerable<Entry> GetEntries(Path path, Regex mask = null)
{
if (!path.IsDirectory)
throw new InvalidOperationException(string.Format("Path '{0}' is not a directory, thus it has no entries.", path));
List<Entry> result = new List<Entry>();
try
{
var lst = Client.ListDirectory(AdaptPath(Client, path));
foreach (var ls in lst)
{
if (ls.Name == "." || ls.Name == "..")
continue;
Entry e = CreateEntry(path, ls);
if (e != null)
result.Add(e);
}
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return result;
}
/// <summary>Get entry located under given path.</summary>
/// <param name="path">Path to get.</param>
/// <returns>Returns entry information.</returns>
public override Entry GetEntry(Path path)
{
Entry result = null;
try
{
if (Client.Exists(AdaptPath(Client, path)))
result = CreateEntry(path, Client.Get(AdaptPath(Client, path)), false);
}
catch (Exception)
{
}
return result;
}
/// <summary>Change name of the entry under specified path.</summary>
/// <param name="path">The path to entry which name will be changed.</param>
/// <param name="newName">The new name of entry.</param>
public override void ReName(Path path, string newName)
{
base.ReName(path, newName);
try
{
Client.RenameFile(AdaptPath(Client, path), AdaptPath(Client, path.Parent + newName));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
}
/// <summary>Create Directory and return new entry.</summary>
/// <param name="path">Path of directory.</param>
/// <returns>Created entry.</returns>
public override Directory DirectoryCreate(Path path)
{
base.DirectoryCreate(path);
try
{
Client.CreateDirectory(AdaptPath(Client, path));
if (Client.Exists(AdaptPath(Client, path)))
return CreateEntry(path, Client.Get(AdaptPath(Client, path)), false) as Directory;
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Deletes the specified directory and, if
/// indicated, any subdirectories in the directory.</summary>
/// <param name="path">The path of the directory to remove.</param>
/// <param name="recursive">Set <c>true</c> to remove directories,
/// subdirectories, and files in path; otherwise <c>false</c>.</param>
public override void DirectoryDelete(Path path, bool recursive = false)
{
base.DirectoryDelete(path, recursive);
try
{
if (Client.Exists(AdaptPath(Client, path)))
Client.DeleteDirectory(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
}
/// <summary>Opens an existing file for reading.</summary>
/// <param name="path">The file to be opened for reading.</param>
/// <returns>A read-only <see cref="Stream" /> on the specified path.</returns>
public override Stream FileOpenRead(Path path)
{
base.FileOpenRead(path);
try
{
return Client.OpenRead(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Opens an existing file for writing.</summary>
/// <param name="path">The file to be opened for writing.</param>
/// <param name="append">Append file.</param>
/// <returns>An unshared <see cref="Stream" /> object on
/// the specified path with write access.</returns>
public override Stream FileOpenWrite(Path path, bool append = false)
{
base.FileOpenWrite(path, append);
try
{
return Client.OpenWrite(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override File FileTouch(Path path, bool overwrite = false)
{
base.FileTouch(path, overwrite);
try
{
using (var fs = Client.OpenWrite(AdaptPath(Client, path)))
fs.Close();
return new File
{
CreationTime = DateTime.Now,
LastWriteTime = DateTime.Now,
LastAccessTime = DateTime.Now,
Size = 0,
IsReadOnly = false,
Path = path,
FileSystem = this,
Data = null,
};
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Creates or overwrites a file in the specified path.</summary>
/// <param name="path">The path and name of the file to create.</param>
/// <param name="overwrite">If file exists replace it.</param>
/// <returns>A <see cref="Stream" /> that provides
/// write access to the file specified in path.</returns>
public override Stream FileCreate(Path path, bool overwrite = false)
{
base.FileCreate(path, overwrite);
try
{
return Client.OpenWrite(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
return null;
}
/// <summary>Deletes the specified file. An exception is not thrown
/// if the specified file does not exist.</summary>
/// <param name="path">The path of the file to be deleted.</param>
public override void FileDelete(Path path)
{
base.FileDelete(path);
try
{
if (Client.Exists(AdaptPath(Client, path)))
Client.DeleteFile(AdaptPath(Client, path));
}
catch (Exception ex)
{
_exHandler(ex);
if (_client != null)
{
_client.Dispose();
_client = null;
}
}
}
}
}