FluentFTP 추가

This commit is contained in:
2018-01-02 00:47:15 +09:00
parent cf54025332
commit 1712ac7b9b
34 changed files with 19190 additions and 89 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
</configuration>
</configuration>

41
FileTransfer.cs Normal file
View File

@@ -0,0 +1,41 @@
using FluentFTP;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NewsCrawler
{
class FileTransfer
{
const string HOST = "mjjo53.us.to";
const string USER = "trader";
const string PASSWORD = "sbtmaoao";
const string REMOTE_BASE_PATH = "/";
public async void SendDir(string localDir, string remoteDir)
{
try
{
using (FtpClient client = new FtpClient(HOST, USER, PASSWORD))
{
client.ConnectTimeout = 3000;
client.RetryAttempts = 3;
string project_path = Path.GetDirectoryName(Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory()));
List<string> files = Directory.GetFiles(project_path + localDir).ToList();
string remotePath = REMOTE_BASE_PATH + remoteDir;
await client.ConnectAsync();
await client.UploadFilesAsync(files, remotePath, FtpExists.Overwrite);
//await client.ChmodAsync(remotePath, 777);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace FluentFTP {
/// <summary>
/// Interface for the FtpClient class. For detailed documentation of the methods, please see the FtpClient class.
/// </summary>
public interface IFtpClient : IDisposable {
// CONNECTION
bool IsDisposed { get; }
FtpIpVersion InternetProtocolVersions { get; set; }
int SocketPollInterval { get; set; }
bool StaleDataCheck { get; set; }
bool IsConnected { get; }
bool EnableThreadSafeDataConnections { get; set; }
Encoding Encoding { get; set; }
string Host { get; set; }
int Port { get; set; }
NetworkCredential Credentials { get; set; }
int MaximumDereferenceCount { get; set; }
X509CertificateCollection ClientCertificates { get; }
Func<string> AddressResolver { get; set; }
IEnumerable<int> ActivePorts { get; set; }
FtpDataConnectionType DataConnectionType { get; set; }
bool UngracefullDisconnection { get; set; }
int ConnectTimeout { get; set; }
int ReadTimeout { get; set; }
int DataConnectionConnectTimeout { get; set; }
int DataConnectionReadTimeout { get; set; }
bool SocketKeepAlive { get; set; }
FtpCapability Capabilities { get; }
FtpHashAlgorithm HashAlgorithms { get; }
FtpEncryptionMode EncryptionMode { get; set; }
bool DataConnectionEncryption { get; set; }
SslProtocols SslProtocols { get; set; }
string SystemType { get; }
string ConnectionType { get; }
FtpParser ListingParser { get; set; }
CultureInfo ListingCulture { get; set; }
double TimeOffset { get; set; }
bool RecursiveList { get; set; }
bool BulkListing { get; set; }
int BulkListingLength { get; set; }
int TransferChunkSize { get; set; }
int RetryAttempts { get; set; }
uint UploadRateLimit { get; set; }
uint DownloadRateLimit { get; set; }
FtpDataType UploadDataType { get; set; }
FtpDataType DownloadDataType { get; set; }
event FtpSslValidation ValidateCertificate;
FtpReply Execute(string command);
FtpReply GetReply();
void Connect();
void Disconnect();
bool HasFeature(FtpCapability cap);
void DisableUTF8();
// MANAGEMENT
void DeleteFile(string path);
void DeleteDirectory(string path);
void DeleteDirectory(string path, FtpListOption options);
bool DirectoryExists(string path);
bool FileExists(string path);
void CreateDirectory(string path);
void CreateDirectory(string path, bool force);
void Rename(string path, string dest);
bool MoveFile(string path, string dest, FtpExists existsMode = FtpExists.Overwrite);
bool MoveDirectory(string path, string dest, FtpExists existsMode = FtpExists.Overwrite);
void SetFilePermissions(string path, int permissions);
void Chmod(string path, int permissions);
void SetFilePermissions(string path, FtpPermission owner, FtpPermission group, FtpPermission other);
void Chmod(string path, FtpPermission owner, FtpPermission group, FtpPermission other);
FtpListItem GetFilePermissions(string path);
int GetChmod(string path);
FtpListItem DereferenceLink(FtpListItem item);
FtpListItem DereferenceLink(FtpListItem item, int recMax);
void SetWorkingDirectory(string path);
string GetWorkingDirectory();
long GetFileSize(string path);
DateTime GetModifiedTime(string path, FtpDate type = FtpDate.Original);
void SetModifiedTime(string path, DateTime date, FtpDate type = FtpDate.Original);
// LISTING
FtpListItem GetObjectInfo(string path, bool dateModified = false);
FtpListItem[] GetListing();
FtpListItem[] GetListing(string path);
FtpListItem[] GetListing(string path, FtpListOption options);
string[] GetNameListing();
string[] GetNameListing(string path);
// LOW LEVEL
Stream OpenRead(string path);
Stream OpenRead(string path, FtpDataType type);
Stream OpenRead(string path, FtpDataType type, bool checkIfFileExists);
Stream OpenRead(string path, FtpDataType type, long restart);
Stream OpenRead(string path, long restart);
Stream OpenRead(string path, long restart, bool checkIfFileExists);
Stream OpenRead(string path, FtpDataType type, long restart, bool checkIfFileExists);
Stream OpenWrite(string path);
Stream OpenWrite(string path, FtpDataType type);
Stream OpenWrite(string path, FtpDataType type, bool checkIfFileExists);
Stream OpenAppend(string path);
Stream OpenAppend(string path, FtpDataType type);
Stream OpenAppend(string path, FtpDataType type, bool checkIfFileExists);
// HIGH LEVEL
int UploadFiles(IEnumerable<string> localPaths, string remoteDir, FtpExists existsMode = FtpExists.Overwrite, bool createRemoteDir = true, FtpVerify verifyOptions = FtpVerify.None, FtpError errorHandling = FtpError.None);
int UploadFiles(IEnumerable<FileInfo> localFiles, string remoteDir, FtpExists existsMode = FtpExists.Overwrite, bool createRemoteDir = true, FtpVerify verifyOptions = FtpVerify.None, FtpError errorHandling = FtpError.None);
int DownloadFiles(string localDir, IEnumerable<string> remotePaths, bool overwrite = true, FtpVerify verifyOptions = FtpVerify.None, FtpError errorHandling = FtpError.None);
bool UploadFile(string localPath, string remotePath, FtpExists existsMode = FtpExists.Overwrite, bool createRemoteDir = false, FtpVerify verifyOptions = FtpVerify.None, IProgress<double> progress = null);
bool Upload(Stream fileStream, string remotePath, FtpExists existsMode = FtpExists.Overwrite, bool createRemoteDir = false, IProgress<double> progress = null);
bool Upload(byte[] fileData, string remotePath, FtpExists existsMode = FtpExists.Overwrite, bool createRemoteDir = false, IProgress<double> progress = null);
bool DownloadFile(string localPath, string remotePath, bool overwrite = true, FtpVerify verifyOptions = FtpVerify.None, IProgress<double> progress = null);
bool Download(Stream outStream, string remotePath, IProgress<double> progress = null);
bool Download(out byte[] outBytes, string remotePath, IProgress<double> progress = null);
// HASH
FtpHashAlgorithm GetHashAlgorithm();
void SetHashAlgorithm(FtpHashAlgorithm type);
FtpHash GetHash(string path);
FtpHash GetChecksum(string path);
string GetMD5(string path);
string GetXCRC(string path);
string GetXMD5(string path);
string GetXSHA1(string path);
string GetXSHA256(string path);
string GetXSHA512(string path);
}
}

View File

@@ -0,0 +1,606 @@
using System;
namespace FluentFTP {
/// <summary>
/// Defines the type of encryption to use
/// </summary>
public enum FtpEncryptionMode {
/// <summary>
/// Plain text.
/// </summary>
None,
/// <summary>
/// FTPS encryption is used from the start of the connection, port 990.
/// </summary>
Implicit,
/// <summary>
/// Connection starts in plain text and FTPS encryption is enabled
/// with the AUTH command immediately after the server greeting.
/// </summary>
Explicit
}
/// <summary>
/// The type of response the server responded with
/// </summary>
public enum FtpResponseType : int {
/// <summary>
/// No response
/// </summary>
None = 0,
/// <summary>
/// Success
/// </summary>
PositivePreliminary = 1,
/// <summary>
/// Success
/// </summary>
PositiveCompletion = 2,
/// <summary>
/// Success
/// </summary>
PositiveIntermediate = 3,
/// <summary>
/// Temporary failure
/// </summary>
TransientNegativeCompletion = 4,
/// <summary>
/// Permanent failure
/// </summary>
PermanentNegativeCompletion = 5
}
/// <summary>
/// Server features
/// </summary>
[Flags]
public enum FtpCapability : int {
/// <summary>
/// This server said it doesn't support anything!
/// </summary>
NONE = 0,
/// <summary>
/// Supports the MLST command
/// </summary>
MLSD = 1,
/// <summary>
/// Supports the SIZE command
/// </summary>
SIZE = 2,
/// <summary>
/// Supports the MDTM command
/// </summary>
MDTM = 4,
/// <summary>
/// Supports download/upload stream resumes
/// </summary>
REST = 8,
/// <summary>
/// Supports UTF8
/// </summary>
UTF8 = 16,
/// <summary>
/// PRET Command used in distributed ftp server software DrFTPD
/// </summary>
PRET = 32,
/// <summary>
/// Server supports the MFMT command for setting the
/// modified date of an object on the server
/// </summary>
MFMT = 64,
/// <summary>
/// Server supports the MFCT command for setting the
/// created date of an object on the server
/// </summary>
MFCT = 128,
/// <summary>
/// Server supports the MFF command for setting certain facts
/// about file system objects. If you need this command, it would
/// probably be handy to query FEAT your self and have a look at
/// the FtpReply.InfoMessages property to see which facts the server
/// allows you to modify.
/// </summary>
MFF = 256,
/// <summary>
/// Server supports the STAT command
/// </summary>
STAT = 512,
/// <summary>
/// Support for the HASH command
/// </summary>
HASH = 1024,
/// <summary>
/// Support for the non-standard MD5 command
/// </summary>
MD5 = 2048,
/// <summary>
/// Support for the non-standard XMD5 command
/// </summary>
XMD5 = 4096,
/// <summary>
/// Support for the non-standard XCRC command
/// </summary>
XCRC = 8192,
/// <summary>
/// Support for the non-standard XSHA1 command
/// </summary>
XSHA1 = 16384,
/// <summary>
/// Support for the non-standard XSHA256 command
/// </summary>
XSHA256 = 32768,
/// <summary>
/// Support for the non-standard XSHA512 command
/// </summary>
XSHA512 = 65536
}
/// <summary>
/// Different types of hashing algorithms for computing checksums.
/// </summary>
[Flags]
public enum FtpHashAlgorithm : int {
/// <summary>
/// HASH command is not supported
/// </summary>
NONE = 0,
/// <summary>
/// SHA-1
/// </summary>
SHA1 = 1,
/// <summary>
/// SHA-256
/// </summary>
SHA256 = 2,
/// <summary>
/// SHA-512
/// </summary>
SHA512 = 4,
/// <summary>
/// MD5
/// </summary>
MD5 = 8,
/// <summary>
/// CRC
/// </summary>
CRC = 16
}
/// <summary>
/// IP Versions to allow when connecting
/// to a server.
/// </summary>
[Flags]
public enum FtpIpVersion : int {
/// <summary>
/// Internet Protocol Version 4
/// </summary>
IPv4 = 1,
/// <summary>
/// Internet Protocol Version 6
/// </summary>
IPv6 = 2,
/// <summary>
/// Allow any supported version
/// </summary>
ANY = IPv4 | IPv6
}
/// <summary>
/// Data connection type
/// </summary>
public enum FtpDataConnectionType {
/// <summary>
/// This type of data connection attempts to use the EPSV command
/// and if the server does not support EPSV it falls back to the
/// PASV command before giving up unless you are connected via IPv6
/// in which case the PASV command is not supported.
/// </summary>
AutoPassive,
/// <summary>
/// Passive data connection. EPSV is a better
/// option if it's supported. Passive connections
/// connect to the IP address dictated by the server
/// which may or may not be accessible by the client
/// for example a server behind a NAT device may
/// give an IP address on its local network that
/// is inaccessible to the client. Please note that IPv6
/// does not support this type data connection. If you
/// ask for PASV and are connected via IPv6 EPSV will
/// automatically be used in its place.
/// </summary>
PASV,
/// <summary>
/// Same as PASV except the host supplied by the server is ignored
/// and the data connection is made to the same address that the control
/// connection is connected to. This is useful in scenarios where the
/// server supplies a private/non-routable network address in the
/// PASV response. It's functionally identical to EPSV except some
/// servers may not implement the EPSV command. Please note that IPv6
/// does not support this type data connection. If you
/// ask for PASV and are connected via IPv6 EPSV will
/// automatically be used in its place.
/// </summary>
PASVEX,
/// <summary>
/// Extended passive data connection, recommended. Works
/// the same as a PASV connection except the server
/// does not dictate an IP address to connect to, instead
/// the passive connection goes to the same address used
/// in the control connection. This type of data connection
/// supports IPv4 and IPv6.
/// </summary>
EPSV,
/// <summary>
/// This type of data connection attempts to use the EPRT command
/// and if the server does not support EPRT it falls back to the
/// PORT command before giving up unless you are connected via IPv6
/// in which case the PORT command is not supported.
/// </summary>
AutoActive,
/// <summary>
/// Active data connection, not recommended unless
/// you have a specific reason for using this type.
/// Creates a listening socket on the client which
/// requires firewall exceptions on the client system
/// as well as client network when connecting to a
/// server outside of the client's network. In addition
/// the IP address of the interface used to connect to the
/// server is the address the server is told to connect to
/// which, if behind a NAT device, may be inaccessible to
/// the server. This type of data connection is not supported
/// by IPv6. If you specify PORT and are connected via IPv6
/// EPRT will automatically be used instead.
/// </summary>
PORT,
/// <summary>
/// Extended active data connection, not recommended
/// unless you have a specific reason for using this
/// type. Creates a listening socket on the client
/// which requires firewall exceptions on the client
/// as well as client network when connecting to a
/// server outside of the client's network. The server
/// connects to the IP address it sees the client coming
/// from. This type of data connection supports IPv4 and IPv6.
/// </summary>
EPRT
}
/// <summary>
/// Type of data transfer to do
/// </summary>
public enum FtpDataType {
/// <summary>
/// ASCII transfer
/// </summary>
ASCII,
/// <summary>
/// Binary transfer
/// </summary>
Binary
}
/// <summary>
/// Type of file system of object
/// </summary>
public enum FtpFileSystemObjectType {
/// <summary>
/// A file
/// </summary>
File,
/// <summary>
/// A directory
/// </summary>
Directory,
/// <summary>
/// A symbolic link
/// </summary>
Link
}
/// <summary>
/// Types of file permissions
/// </summary>
[Flags]
public enum FtpPermission : uint {
/// <summary>
/// No access
/// </summary>
None = 0,
/// <summary>
/// Executable
/// </summary>
Execute = 1,
/// <summary>
/// Writable
/// </summary>
Write = 2,
/// <summary>
/// Readable
/// </summary>
Read = 4
}
/// <summary>
/// Types of special UNIX permissions
/// </summary>
[Flags]
public enum FtpSpecialPermissions : int {
/// <summary>
/// No special permissions are set
/// </summary>
None = 0,
/// <summary>
/// Sticky bit is set
/// </summary>
Sticky = 1,
/// <summary>
/// SGID bit is set
/// </summary>
SetGroupID = 2,
/// <summary>
/// SUID bit is set
/// </summary>
SetUserID = 4
}
/// <summary>
/// The type of response the server responded with
/// </summary>
public enum FtpParser : int {
/// <summary>
/// Use the legacy parser (for older projects that depend on the pre-2017 parser routines).
/// </summary>
Legacy = -1,
/// <summary>
/// Automatically detect the file listing parser to use based on the FTP server (SYST command).
/// </summary>
Auto = 0,
/// <summary>
/// Machine listing parser, works on any FTP server supporting the MLST/MLSD commands.
/// </summary>
Machine = 1,
/// <summary>
/// File listing parser for Windows/IIS.
/// </summary>
Windows = 2,
/// <summary>
/// File listing parser for Unix.
/// </summary>
Unix = 3,
/// <summary>
/// Alternate parser for Unix. Use this if the default one does not work.
/// </summary>
UnixAlt = 4,
/// <summary>
/// File listing parser for Vax/VMS/OpenVMS.
/// </summary>
VMS = 5,
/// <summary>
/// File listing parser for IBM OS400.
/// </summary>
IBM = 6,
/// <summary>
/// File listing parser for Tandem/Nonstop Guardian OS.
/// </summary>
NonStop = 7
}
/// <summary>
/// Flags that can dictate how a file listing is performed
/// </summary>
[Flags]
public enum FtpListOption {
/// <summary>
/// Tries machine listings (MDTM command) if supported,
/// and if not then falls back to OS-specific listings (LIST command)
/// </summary>
Auto = 0,
/// <summary>
/// Load the modify date using MDTM when it could not
/// be parsed from the server listing. This only pertains
/// to servers that do not implement the MLSD command.
/// </summary>
Modify = 1,
/// <summary>
/// Load the file size using the SIZE command when it
/// could not be parsed from the server listing. This
/// only pertains to servers that do not support the
/// MLSD command.
/// </summary>
Size = 2,
/// <summary>
/// Combines the Modify and Size flags
/// </summary>
SizeModify = Modify | Size,
/// <summary>
/// Show hidden/dot files. This only pertains to servers
/// that do not support the MLSD command. This option
/// makes use the non standard -a parameter to LIST to
/// tell the server to show hidden files. Since it's a
/// non-standard option it may not always work. MLSD listings
/// have no such option and whether or not a hidden file is
/// shown is at the discretion of the server.
/// </summary>
AllFiles = 4,
/// <summary>
/// Force the use of OS-specific listings (LIST command) even if
/// machine listings (MLSD command) are supported by the server
/// </summary>
ForceList = 8,
/// <summary>
/// Use the NLST command instead of LIST for a reliable file listing
/// </summary>
NameList = 16,
/// <summary>
/// Force the use of the NLST command (the slowest mode) even if machine listings
/// and OS-specific listings are supported by the server
/// </summary>
ForceNameList = ForceList | NameList,
/// <summary>
/// Try to dereference symbolic links, and stored the linked file/directory in FtpListItem.LinkObject
/// </summary>
DerefLinks = 32,
/// <summary>
/// Sets the ForceList flag and uses `LS' instead of `LIST' as the
/// command for getting a directory listing. This option overrides
/// ForceNameList and ignores the AllFiles flag.
/// </summary>
UseLS = 64 | ForceList,
/// <summary>
/// Gets files within subdirectories as well. Adds the -r option to the LIST command.
/// Some servers may not support this feature.
/// </summary>
Recursive = 128,
/// <summary>
/// Do not retrieve path when no path is supplied to GetListing(),
/// instead just execute LIST with no path argument.
/// </summary>
NoPath = 256,
/// <summary>
/// Include two extra items into the listing, for the current directory (".")
/// and the parent directory (".."). Meaningless unless you want these two
/// items for some reason.
/// </summary>
IncludeSelfAndParent = 512
}
/// <summary>
/// Defines the behavior for uploading/downloading files that already exist
/// </summary>
public enum FtpExists {
/// <summary>
/// Do not check if the file exists. A bit faster than the other options. Only use this if you are SURE that the file does not exist on the server.
/// Otherwise it can cause the UploadFile method to hang due to filesize mismatch.
/// </summary>
NoCheck,
/// <summary>
/// Skip the file if it exists, without any more checks.
/// </summary>
Skip,
/// <summary>
/// Overwrite the file if it exists.
/// </summary>
Overwrite,
/// <summary>
/// Append to the file if it exists, by checking the length and adding the missing data.
/// </summary>
Append
}
/// <summary>
/// Defines the level of the tracing message. Depending on the framework version this is translated
/// to an equivalent logging level in System.Diagnostices (if available)
/// </summary>
public enum FtpTraceLevel {
/// <summary>
/// Used for logging Debug or Verbose level messages
/// </summary>
Verbose,
/// <summary>
/// Used for logging Informational messages
/// </summary>
Info,
/// <summary>
/// Used for logging non-fatal or ignorable error messages
/// </summary>
Warn,
/// <summary>
/// Used for logging Error messages that may need investigation
/// </summary>
Error
}
/// <summary>
/// Defines how multi-file processes should handle a processing error.
/// </summary>
/// <remarks><see cref="FtpError.Stop"/> &amp; <see cref="FtpError.Throw"/> Cannot Be Combined</remarks>
[Flags]
public enum FtpError {
/// <summary>
/// No action is taken upon errors. The method absorbs the error and continues.
/// </summary>
None = 0,
/// <summary>
/// If any files have completed successfully (or failed after a partial download/upload) then should be deleted.
/// This will simulate an all-or-nothing transaction downloading or uploading multiple files. If this option is not
/// combined with <see cref="FtpError.Stop"/> or <see cref="FtpError.Throw"/> then the method will
/// continue to process all items whether if they are successful or not and then delete everything if a failure was
/// encountered at any point.
/// </summary>
DeleteProcessed = 1,
/// <summary>
/// The method should stop processing any additional files and immediately return upon encountering an error.
/// Cannot be combined with <see cref="FtpError.Throw"/>
/// </summary>
Stop = 2,
/// <summary>
/// The method should stop processing any additional files and immediately throw the current error.
/// Cannot be combined with <see cref="FtpError.Stop"/>
/// </summary>
Throw = 4,
}
/// <summary>
/// Defines if additional verification and actions upon failure that
/// should be performed when uploading/downloading files using the high-level APIs. Ignored if the
/// FTP server does not support any hashing algorithms.
/// </summary>
[Flags]
public enum FtpVerify {
/// <summary>
/// No verification of the file is performed
/// </summary>
None = 0,
/// <summary>
/// The checksum of the file is verified, if supported by the server.
/// If the checksum comparison fails then we retry the download/upload
/// a specified amount of times before giving up. (See <see cref="FtpClient.RetryAttempts"/>)
/// </summary>
Retry = 1,
/// <summary>
/// The checksum of the file is verified, if supported by the server.
/// If the checksum comparison fails then the failed file will be deleted.
/// If combined with <see cref="FtpVerify.Retry"/>, then
/// the deletion will occur if it fails upon the final retry.
/// </summary>
Delete = 2,
/// <summary>
/// The checksum of the file is verified, if supported by the server.
/// If the checksum comparison fails then an exception will be thrown.
/// If combined with <see cref="FtpVerify.Retry"/>, then the throw will
/// occur upon the failure of the final retry, and/or if combined with <see cref="FtpVerify.Delete"/>
/// the method will throw after the deletion is processed.
/// </summary>
Throw = 4,
/// <summary>
/// The checksum of the file is verified, if supported by the server.
/// If the checksum comparison fails then the method returns false and no other action is taken.
/// </summary>
OnlyChecksum = 8,
}
/// <summary>
/// Defines if additional verification and actions upon failure that
/// should be performed when uploading/downloading files using the high-level APIs. Ignored if the
/// FTP server does not support any hashing algorithms.
/// </summary>
public enum FtpDate {
/// <summary>
/// The date is whatever the server returns, with no conversion performed.
/// </summary>
Original = 0,
#if !CORE
/// <summary>
/// The date is converted to the local timezone, based on the TimeOffset property in FtpClient.
/// </summary>
Local = 1,
#endif
/// <summary>
/// The date is converted to UTC, based on the TimeOffset property in FtpClient.
/// </summary>
UTC = 2,
}
}

View File

@@ -0,0 +1,92 @@
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 NET45
using System.Threading.Tasks;
#endif
namespace FluentFTP {
/// <summary>
/// Event is fired when a SSL certificate needs to be validated
/// </summary>
/// <param name="control">The control connection that triggered the event</param>
/// <param name="e">Event args</param>
public delegate void FtpSslValidation(FtpClient control, FtpSslValidationEventArgs e);
/// <summary>
/// Event fired if a bad SSL certificate is encountered. This even is used internally; if you
/// don't have a specific reason for using it you are probably looking for FtpSslValidation.
/// </summary>
/// <param name="stream"></param>
/// <param name="e"></param>
public delegate void FtpSocketStreamSslValidation(FtpSocketStream stream, FtpSslValidationEventArgs e);
/// <summary>
/// Event args for the FtpSslValidationError delegate
/// </summary>
public class FtpSslValidationEventArgs : EventArgs {
X509Certificate m_certificate = null;
/// <summary>
/// The certificate to be validated
/// </summary>
public X509Certificate Certificate {
get {
return m_certificate;
}
set {
m_certificate = value;
}
}
X509Chain m_chain = null;
/// <summary>
/// The certificate chain
/// </summary>
public X509Chain Chain {
get {
return m_chain;
}
set {
m_chain = value;
}
}
SslPolicyErrors m_policyErrors = SslPolicyErrors.None;
/// <summary>
/// Validation errors, if any.
/// </summary>
public SslPolicyErrors PolicyErrors {
get {
return m_policyErrors;
}
set {
m_policyErrors = value;
}
}
bool m_accept = false;
/// <summary>
/// Gets or sets a value indicating if this certificate should be accepted. The default
/// value is false. If the certificate is not accepted, an AuthenticationException will
/// be thrown.
/// </summary>
public bool Accept {
get {
return m_accept;
}
set {
m_accept = value;
}
}
}
}

View File

@@ -0,0 +1,127 @@
using System;
#if !CORE
using System.Runtime.Serialization;
#endif
namespace FluentFTP {
/// <summary>
/// FTP related error
/// </summary>
#if !CORE
[Serializable]
#endif
public class FtpException : Exception {
/// <summary>
/// Initializes a new instance of the <see cref="FtpException"/> class.
/// </summary>
/// <param name="message">The error message</param>
public FtpException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="FtpException"/> class with an inner exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public FtpException(string message, Exception innerException) : base(message, innerException) { }
#if !CORE
/// <summary>
/// Must be implemented so every Serializer can Deserialize the Exception
/// </summary>
protected FtpException(SerializationInfo info, StreamingContext context) : base(info, context) { }
#endif
}
/// <summary>
/// Exception triggered on command failures
/// </summary>
#if !CORE
[Serializable]
#endif
public class FtpCommandException : FtpException {
string _code = null;
/// <summary>
/// Gets the completion code associated with the response
/// </summary>
public string CompletionCode {
get { return _code; }
private set { _code = value; }
}
/// <summary>
/// The type of response received from the last command executed
/// </summary>
public FtpResponseType ResponseType {
get {
if (_code != null) {
// we only care about error types, if an exception
// is being thrown for a successful response there
// is a problem.
switch (_code[0]) {
case '4':
return FtpResponseType.TransientNegativeCompletion;
case '5':
return FtpResponseType.PermanentNegativeCompletion;
}
}
return FtpResponseType.None;
}
}
/// <summary>
/// Initializes a new instance of a FtpResponseException
/// </summary>
/// <param name="code">Status code</param>
/// <param name="message">Associated message</param>
public FtpCommandException(string code, string message)
: base(message) {
CompletionCode = code;
}
/// <summary>
/// Initializes a new instance of a FtpResponseException
/// </summary>
/// <param name="reply">The FtpReply to build the exception from</param>
public FtpCommandException(FtpReply reply)
: this(reply.Code, reply.ErrorMessage) {
}
#if !CORE
/// <summary>
/// Must be implemented so every Serializer can Deserialize the Exception
/// </summary>
protected FtpCommandException(SerializationInfo info, StreamingContext context) : base(info, context) { }
#endif
}
/// <summary>
/// Exception is thrown when encryption could not be negotiated by the server
/// </summary>
#if !CORE
[Serializable]
#endif
public class FtpSecurityNotAvailableException : FtpException {
/// <summary>
/// Default constructor
/// </summary>
public FtpSecurityNotAvailableException()
: base("Security is not available on the server.") {
}
/// <summary>
/// Custom error message
/// </summary>
/// <param name="message">Error message</param>
public FtpSecurityNotAvailableException(string message)
: base(message) {
}
#if !CORE
/// <summary>
/// Must be implemented so every Serializer can Deserialize the Exception
/// </summary>
protected FtpSecurityNotAvailableException(SerializationInfo info, StreamingContext context) : base(info, context) { }
#endif
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace FluentFTP {
/// <summary>
/// Represents a computed hash of an object
/// on the FTP server. See the following link
/// for more information:
/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
/// </summary>
public class FtpHash {
FtpHashAlgorithm m_algorithm = FtpHashAlgorithm.NONE;
/// <summary>
/// Gets the algorithm that was used to compute the hash
/// </summary>
public FtpHashAlgorithm Algorithm {
get { return m_algorithm; }
internal set { m_algorithm = value; }
}
string m_value = null;
/// <summary>
/// Gets the computed hash returned by the server
/// </summary>
public string Value {
get { return m_value; }
internal set { m_value = value; }
}
/// <summary>
/// Gets a value indicating if this object represents a
/// valid hash response from the server.
/// </summary>
public bool IsValid {
get { return m_algorithm != FtpHashAlgorithm.NONE && !string.IsNullOrEmpty(m_value); }
}
/// <summary>
/// Computes the hash for the specified file and compares
/// it to the value in this object. CRC hashes are not supported
/// because there is no built-in support in the .net framework and
/// a CRC implementation exceeds the scope of this project. If you
/// attempt to call this on a CRC hash a <see cref="NotImplementedException"/> will
/// be thrown.
/// </summary>
/// <param name="file">The file to compute the hash for</param>
/// <returns>True if the computed hash matches what's stored in this object.</returns>
/// <exception cref="NotImplementedException">Thrown if called on a CRC Hash</exception>
public bool Verify(string file) {
using (FileStream istream = new FileStream(file, FileMode.Open, FileAccess.Read)) {
return Verify(istream);
}
}
/// <summary>
/// Computes the hash for the specified stream and compares
/// it to the value in this object. CRC hashes are not supported
/// because there is no built-in support in the .net framework and
/// a CRC implementation exceeds the scope of this project. If you
/// attempt to call this on a CRC hash a <see cref="NotImplementedException"/> will
/// be thrown.
/// </summary>
/// <param name="istream">The stream to compute the hash for</param>
/// <returns>True if the computed hash matches what's stored in this object.</returns>
/// <exception cref="NotImplementedException">Thrown if called on a CRC Hash</exception>
public bool Verify(Stream istream) {
if (IsValid) {
HashAlgorithm hashAlg = null;
switch (m_algorithm) {
case FtpHashAlgorithm.SHA1:
#if CORE
hashAlg = SHA1.Create();
#else
hashAlg = new SHA1CryptoServiceProvider();
#endif
break;
#if !NET20
case FtpHashAlgorithm.SHA256:
#if CORE
hashAlg = SHA256.Create();
#else
hashAlg = new SHA256CryptoServiceProvider();
#endif
break;
case FtpHashAlgorithm.SHA512:
#if CORE
hashAlg = SHA512.Create();
#else
hashAlg = new SHA512CryptoServiceProvider();
#endif
break;
#endif
case FtpHashAlgorithm.MD5:
#if CORE
hashAlg = MD5.Create();
#else
hashAlg = new MD5CryptoServiceProvider();
#endif
break;
case FtpHashAlgorithm.CRC:
throw new NotImplementedException("There is no built in support for computing CRC hashes.");
default:
throw new NotImplementedException("Unknown hash algorithm: " + m_algorithm.ToString());
}
try {
byte[] data = null;
string hash = "";
data = hashAlg.ComputeHash(istream);
if (data != null) {
foreach (byte b in data) {
hash += b.ToString("x2");
}
return (hash.ToUpper() == m_value.ToUpper());
}
} finally {
#if !NET20 && !NET35 // .NET 2.0 doesn't provide access to Dispose() for HashAlgorithm
if (hashAlg != null)
hashAlg.Dispose();
#endif
}
}
return false;
}
/// <summary>
/// Creates an empty instance.
/// </summary>
internal FtpHash() {
}
}
}

View File

@@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Threading;
namespace FluentFTP {
/// <summary>
/// Represents a file system object on the server
/// </summary>
/// <example><code source="..\Examples\CustomParser.cs" lang="cs" /></example>
public class FtpListItem {
/// <summary>
/// Blank constructor; Fill args manually.
///
/// NOTE TO USER : You should not need to construct this class manually except in advanced cases. Typically constructed by GetListing().
/// </summary>
public FtpListItem() {
}
/// <summary>
/// Constructor with mandatory args filled.
///
/// NOTE TO USER : You should not need to construct this class manually except in advanced cases. Typically constructed by GetListing().
/// </summary>
public FtpListItem(string raw, string name, long size, bool isDir, ref DateTime lastModifiedTime) {
m_input = raw;
m_name = name;
m_size = size;
m_type = isDir ? FtpFileSystemObjectType.Directory : FtpFileSystemObjectType.File;
m_modified = lastModifiedTime;
}
FtpFileSystemObjectType m_type = 0;
/// <summary>
/// Gets the type of file system object.
/// </summary>
public FtpFileSystemObjectType Type {
get {
return m_type;
}
set {
m_type = value;
}
}
string m_path = null;
/// <summary>
/// Gets the full path name to the object.
/// </summary>
public string FullName {
get {
return m_path;
}
set {
m_path = value;
}
}
string m_name = null;
/// <summary>
/// Gets the name of the object.
/// </summary>
public string Name {
get {
if (m_name == null && m_path != null)
return m_path.GetFtpFileName();
return m_name;
}
set {
m_name = value;
}
}
string m_linkTarget = null;
/// <summary>
/// Gets the target a symbolic link points to.
/// </summary>
public string LinkTarget {
get {
return m_linkTarget;
}
set {
m_linkTarget = value;
}
}
int m_linkCount = 0;
/// <summary>
/// Gets the number of links pointing to this file. Only supplied by Unix servers.
/// </summary>
public int LinkCount {
get {
return m_linkCount;
}
set {
m_linkCount = value;
}
}
FtpListItem m_linkObject = null;
/// <summary>
/// Gets the object that the LinkTarget points to. This property is null unless you pass the
/// <see cref="FtpListOption.DerefLinks"/> flag in which case GetListing() will try to resolve
/// the target itself.
/// </summary>
public FtpListItem LinkObject {
get {
return m_linkObject;
}
set {
m_linkObject = value;
}
}
DateTime m_modified = DateTime.MinValue;
/// <summary>
/// Gets the last write time of the object.
/// </summary>
public DateTime Modified {
get {
return m_modified;
}
set {
m_modified = value;
}
}
DateTime m_created = DateTime.MinValue;
/// <summary>
/// Gets the created date of the object.
/// </summary>
public DateTime Created {
get {
return m_created;
}
set {
m_created = value;
}
}
long m_size = -1;
/// <summary>
/// Gets the size of the object.
/// </summary>
public long Size {
get {
return m_size;
}
set {
m_size = value;
}
}
FtpSpecialPermissions m_specialPermissions = FtpSpecialPermissions.None;
/// <summary>
/// Gets special UNIX permissions such as Sticky, SUID and SGID.
/// </summary>
public FtpSpecialPermissions SpecialPermissions {
get {
return m_specialPermissions;
}
set {
m_specialPermissions = value;
}
}
FtpPermission m_ownerPermissions = FtpPermission.None;
/// <summary>
/// Gets the owner permissions.
/// </summary>
public FtpPermission OwnerPermissions {
get {
return m_ownerPermissions;
}
set {
m_ownerPermissions = value;
}
}
FtpPermission m_groupPermissions = FtpPermission.None;
/// <summary>
/// Gets the group permissions.
/// </summary>
public FtpPermission GroupPermissions {
get {
return m_groupPermissions;
}
set {
m_groupPermissions = value;
}
}
FtpPermission m_otherPermissions = FtpPermission.None;
/// <summary>
/// Gets the others permissions.
/// </summary>
public FtpPermission OthersPermissions {
get {
return m_otherPermissions;
}
set {
m_otherPermissions = value;
}
}
string m_rawPermissions = null;
/// <summary>
/// Gets the raw string received for the file permissions.
/// Use this if the other properties are blank/invalid.
/// </summary>
public string RawPermissions {
get {
return m_rawPermissions;
}
set {
m_rawPermissions = value;
}
}
int m_chmod = 0;
/// <summary>
/// Gets the file permissions in the CHMOD format.
/// </summary>
public int Chmod {
get {
return m_chmod;
}
set {
m_chmod = value;
}
}
/// <summary>
/// Gets the raw string received for the file's GROUP permissions.
/// Use this if the other properties are blank/invalid.
/// </summary>
public string RawGroup = null;
/// <summary>
/// Gets the raw string received for the file's OWNER permissions.
/// Use this if the other properties are blank/invalid.
/// </summary>
public string RawOwner = null;
string m_input = null;
/// <summary>
/// Gets the input string that was parsed to generate the
/// values in this object.
/// </summary>
public string Input {
get {
return m_input;
}
set {
m_input = value;
}
}
/// <summary>
/// Returns a string representation of this object and its properties
/// </summary>
/// <returns>A string representing this object</returns>
public override string ToString() {
StringBuilder sb = new StringBuilder();
if (Type == FtpFileSystemObjectType.File) {
sb.Append("FILE");
} else if (Type == FtpFileSystemObjectType.Directory) {
sb.Append("DIR ");
} else if (Type == FtpFileSystemObjectType.Link) {
sb.Append("LINK");
}
sb.Append(" ");
sb.Append(Name);
if (Type == FtpFileSystemObjectType.File) {
sb.Append(" ");
sb.Append("(");
sb.Append(Size.FileSizeToString());
sb.Append(")");
}
if (Created != DateTime.MinValue) {
sb.Append(" ");
sb.Append("Created : ");
sb.Append(Created.ToString());
}
if (Modified != DateTime.MinValue) {
sb.Append(" ");
sb.Append("Modified : ");
sb.Append(Modified.ToString());
}
return sb.ToString();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace FluentFTP {
/// <summary>
/// Represents a reply to an event on the server
/// </summary>
public struct FtpReply {
/// <summary>
/// The type of response received from the last command executed
/// </summary>
public FtpResponseType Type {
get {
int code;
if (Code != null && Code.Length > 0 &&
int.TryParse(Code[0].ToString(), out code)) {
return (FtpResponseType)code;
}
return FtpResponseType.None;
}
}
string m_respCode;
/// <summary>
/// The status code of the response
/// </summary>
public string Code {
get {
return m_respCode;
}
set {
m_respCode = value;
}
}
string m_respMessage;
/// <summary>
/// The message, if any, that the server sent with the response
/// </summary>
public string Message {
get {
return m_respMessage;
}
set {
m_respMessage = value;
}
}
string m_infoMessages;
/// <summary>
/// Informational messages sent from the server
/// </summary>
public string InfoMessages {
get {
return m_infoMessages;
}
set {
m_infoMessages = value;
}
}
/// <summary>
/// General success or failure of the last command executed
/// </summary>
public bool Success {
get {
if (Code != null && Code.Length > 0) {
int i;
// 1xx, 2xx, 3xx indicate success
// 4xx, 5xx are failures
if (int.TryParse(Code[0].ToString(), out i) && i >= 1 && i <= 3) {
return true;
}
}
return false;
}
}
/// <summary>
/// Gets the error message including any informational output
/// that was sent by the server. Sometimes the final response
/// line doesn't contain anything informative as to what was going
/// on with the server. Instead it may send information messages so
/// in an effort to give as meaningful as a response as possible
/// the informational messages will be included in the error.
/// </summary>
public string ErrorMessage {
get {
string message = "";
if (Success) {
return message;
}
if (InfoMessages != null && InfoMessages.Length > 0) {
foreach (string s in InfoMessages.Split('\n')) {
string m = Regex.Replace(s, "^[0-9]{3}-", "");
message += (m.Trim() + "; ");
}
}
message += Message;
return message;
}
}
}
}

View File

@@ -0,0 +1,275 @@
#define TRACE
using System;
using System.Diagnostics;
using System.IO;
namespace FluentFTP {
/// <summary>
/// Used for transaction logging and debug information.
/// </summary>
public static class FtpTrace {
#if !CORE
private static volatile TraceSource m_traceSource = new TraceSource("FluentFTP") {
Switch = new SourceSwitch("sourceSwitch", "Verbose") { Level = SourceLevels.All }
};
static bool m_flushOnWrite = true;
/// <summary>
/// Should the trace listeners be flushed immediately after writing to them?
/// </summary>
public static bool FlushOnWrite {
get { return m_flushOnWrite; }
set { m_flushOnWrite = value; }
}
static bool m_prefix = false;
/// <summary>
/// Should the log entries be written with a prefix of "FluentFTP"?
/// Useful if you have a single TraceListener shared across multiple libraries.
/// </summary>
public static bool LogPrefix {
get { return m_prefix; }
set { m_prefix = value; }
}
/// <summary>
/// Add a TraceListner to the collection. You can use one of the predefined
/// TraceListeners in the System.Diagnostics namespace, such as ConsoleTraceListener
/// for logging to the console, or you can write your own deriving from
/// System.Diagnostics.TraceListener.
/// </summary>
/// <param name="listener">The TraceListener to add to the collection</param>
public static void AddListener(TraceListener listener) {
lock (m_traceSource) {
m_traceSource.Listeners.Add(listener);
}
}
/// <summary>
/// Remove the specified TraceListener from the collection
/// </summary>
/// <param name="listener">The TraceListener to remove from the collection.</param>
public static void RemoveListener(TraceListener listener) {
lock (m_traceSource) {
m_traceSource.Listeners.Remove(listener);
}
}
#endif
#if CORE
static bool m_LogToConsole = false;
/// <summary>
/// Should FTP communication be be logged to console?
/// </summary>
public static bool LogToConsole {
get { return m_LogToConsole; }
set { m_LogToConsole = value; }
}
static string m_LogToFile = null;
/// <summary>
/// Set this to a file path to append all FTP communication to it.
/// </summary>
public static string LogToFile {
get { return m_LogToFile; }
set { m_LogToFile = value; }
}
#endif
static bool m_functions = true;
/// <summary>
/// Should the function calls be logged in Verbose mode?
/// </summary>
public static bool LogFunctions {
get { return m_functions; }
set { m_functions = value; }
}
static bool m_IP = true;
/// <summary>
/// Should the FTP server IP addresses be included in the logs?
/// </summary>
public static bool LogIP {
get { return m_IP; }
set { m_IP = value; }
}
static bool m_username = true;
/// <summary>
/// Should the FTP usernames be included in the logs?
/// </summary>
public static bool LogUserName {
get { return m_username; }
set { m_username = value; }
}
static bool m_password = false;
/// <summary>
/// Should the FTP passwords be included in the logs?
/// </summary>
public static bool LogPassword {
get { return m_password; }
set { m_password = value; }
}
static bool m_tracing = true;
/// <summary>
/// Should we trace at all?
/// </summary>
public static bool EnableTracing
{
get { return m_tracing; }
set { m_tracing = value; }
}
/// <summary>
/// Write to the TraceListeners
/// </summary>
/// <param name="message">The message to write</param>
//[Obsolete("Use overloads with FtpTraceLevel")]
public static void Write(string message) {
Write(FtpTraceLevel.Verbose, message);
}
/// <summary>
/// Write to the TraceListeners
/// </summary>
/// <param name="message">The message to write</param>
//[Obsolete("Use overloads with FtpTraceLevel")]
public static void WriteLine(object message) {
Write(FtpTraceLevel.Verbose, message.ToString());
}
/// <summary>
/// Write to the TraceListeners
/// </summary>
/// <param name="eventType">The type of tracing event</param>
/// <param name="message">The message to write</param>
public static void WriteLine(FtpTraceLevel eventType, object message) {
Write(eventType, message.ToString());
}
/// <summary>
/// Write to the TraceListeners, adding an automatic prefix to the message based on the `eventType`
/// </summary>
/// <param name="eventType">The type of tracing event</param>
/// <param name="message">The message to write</param>
public static void WriteStatus(FtpTraceLevel eventType, object message) {
Write(eventType, TraceLevelPrefix(eventType) + message.ToString());
}
/// <summary>
/// Write to the TraceListeners, for the purpose of logging a API function call
/// </summary>
/// <param name="function">The name of the API function</param>
/// <param name="args">The args passed to the function</param>
public static void WriteFunc(string function, object[] args = null) {
if (m_functions) {
Write(FtpTraceLevel.Verbose, "");
Write(FtpTraceLevel.Verbose, "# " + function + "(" + args.ItemsToString().Join(", ") + ")");
}
}
/// <summary>
/// Write to the TraceListeners
/// </summary>
/// <param name="eventType">The type of tracing event</param>
/// <param name="message">A formattable string to write</param>
public static void Write(FtpTraceLevel eventType, string message) {
if(!EnableTracing)
return;
#if CORE
#if DEBUG
Debug.WriteLine(message);
#else
if (m_LogToConsole) {
Console.WriteLine(message);
}
if (m_LogToFile != null) {
File.AppendAllText(m_LogToFile, message + "\n");
}
#endif
#elif !CORE
if (m_prefix) {
// if prefix is wanted then use TraceEvent()
m_traceSource.TraceEvent(TraceLevelTranslation(eventType), 0, message);
} else {
// if prefix is NOT wanted then write manually
EmitEvent(m_traceSource, TraceLevelTranslation(eventType), message);
}
if (m_flushOnWrite) {
m_traceSource.Flush();
}
#endif
}
private static string TraceLevelPrefix(FtpTraceLevel level) {
switch (level) {
case FtpTraceLevel.Verbose:
return "Status: ";
case FtpTraceLevel.Info:
return "Status: ";
case FtpTraceLevel.Warn:
return "Warning: ";
case FtpTraceLevel.Error:
return "Error: ";
}
return "Status: ";
}
#if !CORE
private static TraceEventType TraceLevelTranslation(FtpTraceLevel level) {
switch (level) {
case FtpTraceLevel.Verbose:
return TraceEventType.Verbose;
case FtpTraceLevel.Info:
return TraceEventType.Information;
case FtpTraceLevel.Warn:
return TraceEventType.Warning;
case FtpTraceLevel.Error:
return TraceEventType.Error;
default:
return TraceEventType.Verbose;
}
}
static object traceSync = new object();
private static void EmitEvent(TraceSource traceSource, TraceEventType eventType, string message) {
try {
lock (traceSync) {
if (traceSource.Switch.ShouldTrace(eventType)) {
foreach (TraceListener listener in traceSource.Listeners) {
try {
listener.WriteLine(message);
listener.Flush();
} catch { }
}
}
}
} catch {
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FluentFTP
{
internal class IntRef
{
public int Value;
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace FluentFTP.Proxy {
/// <summary> A FTP client with a HTTP 1.1 proxy implementation. </summary>
public class FtpClientHttp11Proxy : FtpClientProxy {
/// <summary> A FTP client with a HTTP 1.1 proxy implementation </summary>
/// <param name="proxy">Proxy information</param>
public FtpClientHttp11Proxy(ProxyInfo proxy)
: base(proxy) {
ConnectionType = "HTTP 1.1 Proxy";
}
/// <summary> Redefine the first dialog: HTTP Frame for the HTTP 1.1 Proxy </summary>
protected override void Handshake() {
var proxyConnectionReply = GetReply();
if (!proxyConnectionReply.Success)
throw new FtpException("Can't connect " + Host + " via proxy " + Proxy.Host + ".\nMessage : " +
proxyConnectionReply.ErrorMessage);
}
/// <summary>
/// Creates a new instance of this class. Useful in FTP proxy classes.
/// </summary>
protected override FtpClient Create() {
return new FtpClientHttp11Proxy(Proxy);
}
/// <summary>
/// Connects to the server using an existing <see cref="FtpSocketStream"/>
/// </summary>
/// <param name="stream">The existing socket stream</param>
protected override void Connect(FtpSocketStream stream) {
Connect(stream, Host, Port, FtpIpVersion.ANY);
}
/// <summary>
/// Connects to the server using an existing <see cref="FtpSocketStream"/>
/// </summary>
/// <param name="stream">The existing socket stream</param>
/// <param name="host">Host name</param>
/// <param name="port">Port number</param>
/// <param name="ipVersions">IP version to use</param>
protected override void Connect(FtpSocketStream stream, string host, int port, FtpIpVersion ipVersions) {
base.Connect(stream);
var writer = new StreamWriter(stream);
writer.WriteLine("CONNECT {0}:{1} HTTP/1.1", host, port);
writer.WriteLine("Host: {0}:{1}", host, port);
if (Proxy.Credentials != null) {
var credentialsHash = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Proxy.Credentials.UserName + ":"+ Proxy.Credentials.Password));
writer.WriteLine("Proxy-Authorization: Basic "+ credentialsHash);
}
writer.WriteLine("User-Agent: custom-ftp-client");
writer.WriteLine();
writer.Flush();
ProxyHandshake(stream);
}
private void ProxyHandshake(FtpSocketStream stream) {
var proxyConnectionReply = GetProxyReply(stream);
if (!proxyConnectionReply.Success)
throw new FtpException("Can't connect " + Host + " via proxy " + Proxy.Host + ".\nMessage : " + proxyConnectionReply.ErrorMessage);
}
private FtpReply GetProxyReply( FtpSocketStream stream ) {
FtpReply reply = new FtpReply();
string buf;
#if !CORE14
lock ( Lock ) {
#endif
if( !IsConnected )
throw new InvalidOperationException( "No connection to the server has been established." );
stream.ReadTimeout = ReadTimeout;
while( ( buf = stream.ReadLine( Encoding ) ) != null ) {
Match m;
FtpTrace.WriteLine(FtpTraceLevel.Info, buf);
if( ( m = Regex.Match( buf, @"^HTTP/.*\s(?<code>[0-9]{3}) (?<message>.*)$" ) ).Success ) {
reply.Code = m.Groups[ "code" ].Value;
reply.Message = m.Groups[ "message" ].Value;
break;
}
reply.InfoMessages += ( buf+"\n" );
}
// fixes #84 (missing bytes when downloading/uploading files through proxy)
while( ( buf = stream.ReadLine( Encoding ) ) != null ) {
FtpTrace.WriteLine(FtpTraceLevel.Info, buf);
if (FtpExtensions.IsNullOrWhiteSpace(buf)) {
break;
}
reply.InfoMessages += ( buf+"\n" );
}
#if !CORE14
}
#endif
return reply;
}
}
}

View File

@@ -0,0 +1,22 @@
namespace FluentFTP.Proxy {
/// <summary>
/// Abstraction of an FtpClient with a proxy
/// </summary>
public abstract class FtpClientProxy : FtpClient {
private ProxyInfo _proxy;
/// <summary> The proxy connection info. </summary>
protected ProxyInfo Proxy { get { return _proxy; } }
/// <summary> A FTP client with a HTTP 1.1 proxy implementation </summary>
/// <param name="proxy">Proxy information</param>
protected FtpClientProxy(ProxyInfo proxy) {
_proxy = proxy;
}
/// <summary> Redefine connect for FtpClient : authentication on the Proxy </summary>
/// <param name="stream">The socket stream.</param>
protected override void Connect(FtpSocketStream stream) {
stream.Connect(Proxy.Host, Proxy.Port, InternetProtocolVersions);
}
}
}

View File

@@ -0,0 +1,28 @@
namespace FluentFTP.Proxy {
/// <summary> A FTP client with a user@host proxy identification. </summary>
public class FtpClientUserAtHostProxy : FtpClientProxy {
/// <summary> A FTP client with a user@host proxy identification. </summary>
/// <param name="proxy">Proxy information</param>
public FtpClientUserAtHostProxy(ProxyInfo proxy)
: base(proxy) {
ConnectionType = "User@Host";
}
/// <summary>
/// Creates a new instance of this class. Useful in FTP proxy classes.
/// </summary>
protected override FtpClient Create() {
return new FtpClientUserAtHostProxy(Proxy);
}
/// <summary> Redefine the first dialog: auth with proxy information </summary>
protected override void Handshake() {
// Proxy authentication eventually needed.
if (Proxy.Credentials != null)
Authenticate(Proxy.Credentials.UserName, Proxy.Credentials.Password);
// Connection USER@Host means to change user name to add host.
Credentials.UserName = Credentials.UserName + "@" + Host;
}
}
}

View File

@@ -0,0 +1,42 @@
namespace FluentFTP.Proxy {
/// <summary>
/// A FTP client with a user@host proxy identification, that works with Blue Coat FTP Service servers.
///
/// The 'blue coat variant' forces the client to wait for a 220 FTP response code in
/// the handshake phase.
/// </summary>
public class FtpClientUserAtHostProxyBlueCoat : FtpClientProxy
{
/// <summary> A FTP client with a user@host proxy identification. </summary>
/// <param name="proxy">Proxy information</param>
public FtpClientUserAtHostProxyBlueCoat(ProxyInfo proxy)
: base(proxy)
{
ConnectionType = "User@Host";
}
/// <summary>
/// Creates a new instance of this class. Useful in FTP proxy classes.
/// </summary>
protected override FtpClient Create()
{
return new FtpClientUserAtHostProxyBlueCoat(Proxy);
}
/// <summary> Redefine the first dialog: auth with proxy information </summary>
protected override void Handshake()
{
// Proxy authentication eventually needed.
if (Proxy.Credentials != null)
Authenticate(Proxy.Credentials.UserName, Proxy.Credentials.Password);
// Connection USER@Host means to change user name to add host.
Credentials.UserName = Credentials.UserName + "@" + Host;
FtpReply reply = GetReply();
if (reply.Code == "220")
FtpTrace.WriteLine(FtpTraceLevel.Info, "Status: Server is ready for the new client");
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Net;
namespace FluentFTP.Proxy {
/// <summary> POCO holding proxy information</summary>
public class ProxyInfo {
/// <summary> Proxy host name </summary>
public string Host { get; set; }
/// <summary> Proxy port </summary>
public int Port { get; set; }
/// <summary> Proxy login credentials </summary>
public NetworkCredential Credentials { get; set; }
}
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Threading;
#if ASYNC
using System.Threading.Tasks;
#endif
namespace FluentFTP {
/// <summary>
/// Base class for data stream connections
/// </summary>
public class FtpDataStream : FtpSocketStream {
FtpReply m_commandStatus;
/// <summary>
/// Gets the status of the command that was used to open
/// this data channel
/// </summary>
public FtpReply CommandStatus {
get {
return m_commandStatus;
}
set {
m_commandStatus = value;
}
}
FtpClient m_control = null;
/// <summary>
/// Gets or sets the control connection for this data stream. Setting
/// the control connection causes the object to be cloned and a new
/// connection is made to the server to carry out the task. This ensures
/// that multiple streams can be opened simultaneously.
/// </summary>
public FtpClient ControlConnection {
get {
return m_control;
}
set {
m_control = value;
}
}
long m_length = 0;
/// <summary>
/// Gets or sets the length of the stream. Only valid for file transfers
/// and only valid on servers that support the Size command.
/// </summary>
public override long Length {
get {
return m_length;
}
}
long m_position = 0;
/// <summary>
/// Gets or sets the position of the stream
/// </summary>
public override long Position {
get {
return m_position;
}
set {
throw new InvalidOperationException("You cannot modify the position of a FtpDataStream. This property is updated as data is read or written to the stream.");
}
}
/// <summary>
/// Reads data off the stream
/// </summary>
/// <param name="buffer">The buffer to read into</param>
/// <param name="offset">Where to start in the buffer</param>
/// <param name="count">Number of bytes to read</param>
/// <returns>The number of bytes read</returns>
public override int Read(byte[] buffer, int offset, int count) {
int read = base.Read(buffer, offset, count);
m_position += read;
return read;
}
#if ASYNC
/// <summary>
/// Reads data off the stream asynchronously
/// </summary>
/// <param name="buffer">The buffer to read into</param>
/// <param name="offset">Where to start in the buffer</param>
/// <param name="count">Number of bytes to read</param>
/// <param name="token">The cancellation token for this task</param>
/// <returns>The number of bytes read</returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) {
int read = await base.ReadAsync(buffer, offset, count, token);
m_position += read;
return read;
}
#endif
/// <summary>
/// Writes data to the stream
/// </summary>
/// <param name="buffer">The buffer to write to the stream</param>
/// <param name="offset">Where to start in the buffer</param>
/// <param name="count">The number of bytes to write to the buffer</param>
public override void Write(byte[] buffer, int offset, int count) {
base.Write(buffer, offset, count);
m_position += count;
}
#if ASYNC
/// <summary>
/// Writes data to the stream asynchronously
/// </summary>
/// <param name="buffer">The buffer to write to the stream</param>
/// <param name="offset">Where to start in the buffer</param>
/// <param name="count">The number of bytes to write to the buffer</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) {
await base.WriteAsync(buffer, offset, count, token);
m_position += count;
}
#endif
/// <summary>
/// Sets the length of this stream
/// </summary>
/// <param name="value">Value to apply to the Length property</param>
public override void SetLength(long value) {
m_length = value;
}
/// <summary>
/// Sets the position of the stream. Intended to be used
/// internally by FtpControlConnection.
/// </summary>
/// <param name="pos">The position</param>
public void SetPosition(long pos) {
m_position = pos;
}
/// <summary>
/// Closes the connection and reads the server's reply
/// </summary>
public new FtpReply Close() {
base.Close();
try {
if (ControlConnection != null)
return ControlConnection.CloseDataStream(this);
} finally {
m_commandStatus = new FtpReply();
m_control = null;
}
return new FtpReply();
}
/// <summary>
/// Creates a new data stream object
/// </summary>
/// <param name="conn">The control connection to be used for carrying out this operation</param>
public FtpDataStream(FtpClient conn) : base(conn.SslProtocols) {
if (conn == null)
throw new ArgumentException("The control connection cannot be null.");
ControlConnection = conn;
// always accept certificate no matter what because if code execution ever
// gets here it means the certificate on the control connection object being
// cloned was already accepted.
ValidateCertificate += new FtpSocketStreamSslValidation(delegate (FtpSocketStream obj, FtpSslValidationEventArgs e) {
e.Accept = true;
});
m_position = 0;
}
/// <summary>
/// Finalizer
/// </summary>
~FtpDataStream() {
try {
Dispose(false);
} catch (Exception ex) {
FtpTrace.WriteLine(FtpTraceLevel.Warn, "[Finalizer] Caught and discarded an exception while disposing the FtpDataStream: " + ex.ToString());
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,284 @@
using System;
using System.Collections.Generic;
using System.IO;
#if !CORE
using System.Linq;
using System.Net.Security;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
#endif
namespace FluentFTP {
#if !CORE
/// <summary>
/// .NET SslStream doesn't close TLS connection properly.
/// It does not send the close_notify alert before closing the connection.
/// FtpSslStream uses unsafe code to do that.
/// This is required when we want to downgrade the connection to plaintext using CCC command.
/// Thanks to Neco @ https://stackoverflow.com/questions/237807/net-sslstream-doesnt-close-tls-connection-properly/22626756#22626756
/// </summary>
internal class FtpSslStream : SslStream {
private bool sentCloseNotify = false;
public FtpSslStream(Stream innerStream)
: base(innerStream) {
}
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen)
: base(innerStream, leaveInnerStreamOpen) {
}
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback) {
}
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback)
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback) {
}
#if !NET20 && !NET35
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback, EncryptionPolicy encryptionPolicy)
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback, encryptionPolicy) {
}
#endif
public override void Close() {
try {
if (!sentCloseNotify) {
SslDirectCall.CloseNotify(this);
sentCloseNotify = true;
}
} finally {
base.Close();
}
}
}
internal unsafe static class SslDirectCall {
/// <summary>
/// Send an SSL close_notify alert.
/// </summary>
/// <param name="sslStream"></param>
public static void CloseNotify(SslStream sslStream) {
if (sslStream.IsAuthenticated && sslStream.CanWrite) {
bool isServer = sslStream.IsServer;
byte[] result;
int resultSz;
var asmbSystem = typeof(System.Net.Authorization).Assembly;
int SCHANNEL_SHUTDOWN = 1;
var workArray = BitConverter.GetBytes(SCHANNEL_SHUTDOWN);
var sslstate = FtpReflection.GetField(sslStream, "_SslState");
var context = FtpReflection.GetProperty(sslstate, "Context");
var securityContext = FtpReflection.GetField(context, "m_SecurityContext");
var securityContextHandleOriginal = FtpReflection.GetField(securityContext, "_handle");
SslNativeApi.SSPIHandle securityContextHandle = default(SslNativeApi.SSPIHandle);
securityContextHandle.HandleHi = (IntPtr)FtpReflection.GetField(securityContextHandleOriginal, "HandleHi");
securityContextHandle.HandleLo = (IntPtr)FtpReflection.GetField(securityContextHandleOriginal, "HandleLo");
var credentialsHandle = FtpReflection.GetField(context, "m_CredentialsHandle");
var credentialsHandleHandleOriginal = FtpReflection.GetField(credentialsHandle, "_handle");
SslNativeApi.SSPIHandle credentialsHandleHandle = default(SslNativeApi.SSPIHandle);
credentialsHandleHandle.HandleHi = (IntPtr)FtpReflection.GetField(credentialsHandleHandleOriginal, "HandleHi");
credentialsHandleHandle.HandleLo = (IntPtr)FtpReflection.GetField(credentialsHandleHandleOriginal, "HandleLo");
int bufferSize = 1;
SslNativeApi.SecurityBufferDescriptor securityBufferDescriptor = new SslNativeApi.SecurityBufferDescriptor(bufferSize);
SslNativeApi.SecurityBufferStruct[] unmanagedBuffer = new SslNativeApi.SecurityBufferStruct[bufferSize];
fixed (SslNativeApi.SecurityBufferStruct* ptr = unmanagedBuffer)
fixed (void* workArrayPtr = workArray) {
securityBufferDescriptor.UnmanagedPointer = (void*)ptr;
unmanagedBuffer[0].token = (IntPtr)workArrayPtr;
unmanagedBuffer[0].count = workArray.Length;
unmanagedBuffer[0].type = SslNativeApi.BufferType.Token;
SslNativeApi.SecurityStatus status;
status = (SslNativeApi.SecurityStatus)SslNativeApi.ApplyControlToken(ref securityContextHandle, securityBufferDescriptor);
if (status == SslNativeApi.SecurityStatus.OK) {
unmanagedBuffer[0].token = IntPtr.Zero;
unmanagedBuffer[0].count = 0;
unmanagedBuffer[0].type = SslNativeApi.BufferType.Token;
SslNativeApi.SSPIHandle contextHandleOut = default(SslNativeApi.SSPIHandle);
SslNativeApi.ContextFlags outflags = SslNativeApi.ContextFlags.Zero;
long ts = 0;
var inflags = SslNativeApi.ContextFlags.SequenceDetect |
SslNativeApi.ContextFlags.ReplayDetect |
SslNativeApi.ContextFlags.Confidentiality |
SslNativeApi.ContextFlags.AcceptExtendedError |
SslNativeApi.ContextFlags.AllocateMemory |
SslNativeApi.ContextFlags.InitStream;
if (isServer) {
status = (SslNativeApi.SecurityStatus)SslNativeApi.AcceptSecurityContext(ref credentialsHandleHandle, ref securityContextHandle, null,
inflags, SslNativeApi.Endianness.Native, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts);
} else {
status = (SslNativeApi.SecurityStatus)SslNativeApi.InitializeSecurityContextW(ref credentialsHandleHandle, ref securityContextHandle, null,
inflags, 0, SslNativeApi.Endianness.Native, null, 0, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts);
}
if (status == SslNativeApi.SecurityStatus.OK) {
byte[] resultArr = new byte[unmanagedBuffer[0].count];
Marshal.Copy(unmanagedBuffer[0].token, resultArr, 0, resultArr.Length);
Marshal.FreeCoTaskMem(unmanagedBuffer[0].token);
result = resultArr;
resultSz = resultArr.Length;
} else {
throw new InvalidOperationException(string.Format("AcceptSecurityContext/InitializeSecurityContextW returned [{0}] during CloseNotify.", status));
}
} else {
throw new InvalidOperationException(string.Format("ApplyControlToken returned [{0}] during CloseNotify.", status));
}
}
var innerStream = (Stream)FtpReflection.GetProperty(sslstate, "InnerStream");
innerStream.Write(result, 0, resultSz);
}
}
}
internal unsafe static class SslNativeApi {
internal enum BufferType {
Empty,
Data,
Token,
Parameters,
Missing,
Extra,
Trailer,
Header,
Padding = 9,
Stream,
ChannelBindings = 14,
TargetHost = 16,
ReadOnlyFlag = -2147483648,
ReadOnlyWithChecksum = 268435456
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct SSPIHandle {
public IntPtr HandleHi;
public IntPtr HandleLo;
public bool IsZero {
get {
return this.HandleHi == IntPtr.Zero && this.HandleLo == IntPtr.Zero;
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal void SetToInvalid() {
this.HandleHi = IntPtr.Zero;
this.HandleLo = IntPtr.Zero;
}
public override string ToString() {
return this.HandleHi.ToString("x") + ":" + this.HandleLo.ToString("x");
}
}
[StructLayout(LayoutKind.Sequential)]
internal class SecurityBufferDescriptor {
public readonly int Version;
public readonly int Count;
public unsafe void* UnmanagedPointer;
public SecurityBufferDescriptor(int count) {
this.Version = 0;
this.Count = count;
this.UnmanagedPointer = null;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityBufferStruct {
public int count;
public BufferType type;
public IntPtr token;
public static readonly int Size = sizeof(SecurityBufferStruct);
}
internal enum SecurityStatus {
OK,
ContinueNeeded = 590610,
CompleteNeeded,
CompAndContinue,
ContextExpired = 590615,
CredentialsNeeded = 590624,
Renegotiate,
OutOfMemory = -2146893056,
InvalidHandle,
Unsupported,
TargetUnknown,
InternalError,
PackageNotFound,
NotOwner,
CannotInstall,
InvalidToken,
CannotPack,
QopNotSupported,
NoImpersonation,
LogonDenied,
UnknownCredentials,
NoCredentials,
MessageAltered,
OutOfSequence,
NoAuthenticatingAuthority,
IncompleteMessage = -2146893032,
IncompleteCredentials = -2146893024,
BufferNotEnough,
WrongPrincipal,
TimeSkew = -2146893020,
UntrustedRoot,
IllegalMessage,
CertUnknown,
CertExpired,
AlgorithmMismatch = -2146893007,
SecurityQosFailed,
SmartcardLogonRequired = -2146892994,
UnsupportedPreauth = -2146892989,
BadBinding = -2146892986
}
[Flags]
internal enum ContextFlags {
Zero = 0,
Delegate = 1,
MutualAuth = 2,
ReplayDetect = 4,
SequenceDetect = 8,
Confidentiality = 16,
UseSessionKey = 32,
AllocateMemory = 256,
Connection = 2048,
InitExtendedError = 16384,
AcceptExtendedError = 32768,
InitStream = 32768,
AcceptStream = 65536,
InitIntegrity = 65536,
AcceptIntegrity = 131072,
InitManualCredValidation = 524288,
InitUseSuppliedCreds = 128,
InitIdentify = 131072,
AcceptIdentify = 524288,
ProxyBindings = 67108864,
AllowMissingBindings = 268435456,
UnverifiedTargetName = 536870912
}
internal enum Endianness {
Network,
Native = 16
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern int ApplyControlToken(ref SSPIHandle contextHandle, [In] [Out] SecurityBufferDescriptor outputBuffer);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
internal unsafe static extern int AcceptSecurityContext(ref SSPIHandle credentialHandle, ref SSPIHandle contextHandle, [In] SecurityBufferDescriptor inputBuffer, [In] ContextFlags inFlags, [In] Endianness endianness, ref SSPIHandle outContextPtr, [In] [Out] SecurityBufferDescriptor outputBuffer, [In] [Out] ref ContextFlags attributes, out long timeStamp);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
internal unsafe static extern int InitializeSecurityContextW(ref SSPIHandle credentialHandle, ref SSPIHandle contextHandle, [In] byte* targetName, [In] ContextFlags inFlags, [In] int reservedI, [In] Endianness endianness, [In] SecurityBufferDescriptor inputBuffer, [In] int reservedII, ref SSPIHandle outContextPtr, [In] [Out] SecurityBufferDescriptor outputBuffer, [In] [Out] ref ContextFlags attributes, out long timeStamp);
}
#endif
}

View File

@@ -0,0 +1,363 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;
#if (CORE || NETFX)
using System.Diagnostics;
#endif
#if NET45
using System.Threading.Tasks;
using System.Collections;
#endif
namespace FluentFTP {
/// <summary>
/// Extension methods related to FTP tasks
/// </summary>
public static class FtpExtensions {
/// <summary>
/// Converts the specified path into a valid FTP file system path
/// </summary>
/// <param name="path">The file system path</param>
/// <returns>A path formatted for FTP</returns>
public static string GetFtpPath(this string path) {
if (String.IsNullOrEmpty(path))
return "./";
path = path.Replace('\\', '/');
path = Regex.Replace(path, "[/]+", "/");
path = path.TrimEnd('/');
if (path.Length == 0)
path = "/";
return path;
}
/// <summary>
/// Creates a valid FTP path by appending the specified segments to this string
/// </summary>
/// <param name="path">This string</param>
/// <param name="segments">The path segments to append</param>
/// <returns>A valid FTP path</returns>
public static string GetFtpPath(this string path, params string[] segments) {
if (String.IsNullOrEmpty(path))
path = "./";
foreach (string part in segments) {
if (part != null) {
if (path.Length > 0 && !path.EndsWith("/"))
path += "/";
path += Regex.Replace(part.Replace('\\', '/'), "[/]+", "/").TrimEnd('/');
}
}
path = Regex.Replace(path.Replace('\\', '/'), "[/]+", "/").TrimEnd('/');
if (path.Length == 0)
path = "/";
/*if (!path.StartsWith("/") || !path.StartsWith("./"))
path = "./" + path;*/
return path;
}
/// <summary>
/// Gets the parent directory path (formatted for a FTP server)
/// </summary>
/// <param name="path">The path</param>
/// <returns>The parent directory path</returns>
public static string GetFtpDirectoryName(this string path) {
string tpath = (path == null ? "" : path.GetFtpPath());
if (tpath.Length == 0 || tpath == "/")
return "/";
int lastslash = tpath.LastIndexOf('/');
if (lastslash < 0)
return ".";
if (lastslash == 0)
return "/";
return tpath.Substring(0, lastslash);
}
/*public static string GetFtpDirectoryName(this string path) {
if (path == null || path.Length == 0 || path.GetFtpPath() == "/")
return "/";
return System.IO.Path.GetDirectoryName(path).GetFtpPath();
}*/
/// <summary>
/// Gets the file name and extension from the path
/// </summary>
/// <param name="path">The full path to the file</param>
/// <returns>The file name</returns>
public static string GetFtpFileName(this string path) {
string tpath = (path == null ? null : path);
int lastslash = -1;
if (tpath == null)
return null;
lastslash = tpath.LastIndexOf('/');
if (lastslash < 0)
return tpath;
lastslash += 1;
if (lastslash >= tpath.Length)
return tpath;
return tpath.Substring(lastslash, tpath.Length - lastslash);
}
/*public static string GetFtpFileName(this string path) {
return System.IO.Path.GetFileName(path).GetFtpPath();
}*/
private static string[] FtpDateFormats = { "yyyyMMddHHmmss", "yyyyMMddHHmmss'.'f", "yyyyMMddHHmmss'.'ff", "yyyyMMddHHmmss'.'fff", "MMM dd yyyy","MMM d yyyy","MMM dd HH:mm","MMM d HH:mm" };
/// <summary>
/// Tries to convert the string FTP date representation into a <see cref="DateTime"/> object
/// </summary>
/// <param name="date">The date</param>
/// <param name="style">UTC/Local Time</param>
/// <returns>A <see cref="DateTime"/> object representing the date, or <see cref="DateTime.MinValue"/> if there was a problem</returns>
public static DateTime GetFtpDate(this string date, DateTimeStyles style) {
DateTime parsed;
if (DateTime.TryParseExact(date, FtpDateFormats, CultureInfo.InvariantCulture, style, out parsed)) {
return parsed;
}
return DateTime.MinValue;
}
private static string[] sizePostfix = { "bytes", "KB", "MB", "GB", "TB" };
/// <summary>
/// Converts a file size in bytes to a string representation (eg. 12345 becomes 12.3 KB)
/// </summary>
public static string FileSizeToString(this int bytes) {
return ((long)bytes).FileSizeToString();
}
/// <summary>
/// Converts a file size in bytes to a string representation (eg. 12345 becomes 12.3 KB)
/// </summary>
public static string FileSizeToString(this uint bytes) {
return ((long)bytes).FileSizeToString();
}
/// <summary>
/// Converts a file size in bytes to a string representation (eg. 12345 becomes 12.3 KB)
/// </summary>
public static string FileSizeToString(this ulong bytes) {
return ((long)bytes).FileSizeToString();
}
/// <summary>
/// Converts a file size in bytes to a string representation (eg. 12345 becomes 12.3 KB)
/// </summary>
public static string FileSizeToString(this long bytes) {
int order = 0;
double len = bytes;
while (len >= 1024 && order < sizePostfix.Length - 1) {
order++;
len = len / 1024;
}
return String.Format("{0:0.#} {1}", len, sizePostfix[order]);
}
#if NET45
/// <summary>
/// This creates a <see cref="System.Threading.Tasks.Task{TResult}"/> that represents a pair of begin and end methods
/// that conform to the Asynchronous Programming Model pattern. This extends the maximum amount of arguments from
/// <see cref="o:System.Threading.TaskFactory.FromAsync"/> to 4 from a 3.
/// </summary>
/// <typeparam name="TArg1">The type of the first argument passed to the <paramref name="beginMethod"/> delegate</typeparam>
/// <typeparam name="TArg2">The type of the second argument passed to the <paramref name="beginMethod"/> delegate</typeparam>
/// <typeparam name="TArg3">The type of the third argument passed to the <paramref name="beginMethod"/> delegate</typeparam>
/// <typeparam name="TArg4">The type of the forth argument passed to the <paramref name="beginMethod"/> delegate</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="factory">The <see cref="TaskFactory"/> used</param>
/// <param name="beginMethod">The delegate that begins the asynchronous operation</param>
/// <param name="endMethod">The delegate that ends the asynchronous operation</param>
/// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> delegate</param>
/// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> delegate</param>
/// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> delegate</param>
/// <param name="arg4">The forth argument passed to the <paramref name="beginMethod"/> delegate</param>
/// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> delegate</param>
/// <returns>The created <see cref="System.Threading.Tasks.Task{TResult}"/> that represents the asynchronous operation</returns>
/// <exception cref="System.ArgumentNullException">
/// beginMethod is null
/// or
/// endMethod is null
/// </exception>
public static Task<TResult> FromAsync<TArg1, TArg2, TArg3, TArg4, TResult>(this TaskFactory factory,
Func<TArg1, TArg2, TArg3, TArg4, AsyncCallback, object, IAsyncResult> beginMethod,
Func<IAsyncResult, TResult> endMethod,
TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, object state) {
if (beginMethod == null)
throw new ArgumentNullException("beginMethod");
if (endMethod == null)
throw new ArgumentNullException("endMethod");
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>(state, factory.CreationOptions);
try {
AsyncCallback callback = delegate(IAsyncResult asyncResult) {
tcs.TrySetResult(endMethod(asyncResult));
};
beginMethod(arg1, arg2, arg3, arg4, callback, state);
}
catch {
tcs.TrySetResult(default(TResult));
throw;
}
return tcs.Task;
}
#endif
/// <summary>
/// Validates that the FtpError flags set are not in an invalid combination.
/// </summary>
/// <param name="options">The error handling options set</param>
/// <returns>True if a valid combination, otherwise false</returns>
public static bool IsValidCombination(this FtpError options) {
return options != (FtpError.Stop | FtpError.Throw) &&
options != (FtpError.Throw | FtpError.Stop | FtpError.DeleteProcessed);
}
/// <summary>
/// Checks if every character in the string is whitespace, or the string is null.
/// </summary>
public static bool IsNullOrWhiteSpace(string value) {
if (value == null) return true;
for (int i = 0; i < value.Length; i++) {
if (!Char.IsWhiteSpace(value[i])) return false;
}
return true;
}
/// <summary>
/// Checks if the string is null or 0 length.
/// </summary>
public static bool IsBlank(this string value) {
return value == null || value.Length == 0;
}
#if NET45
/// <summary>
/// Checks if the array is null or 0 length.
/// </summary>
public static bool IsBlank(this IList value) {
return value == null || value.Count == 0;
}
/// <summary>
/// Checks if the array is null or 0 length.
/// </summary>
public static bool IsBlank(this IEnumerable value) {
if (value == null){
return true;
}
if (value is IList){
return ((IList)value).Count == 0;
}
if (value is byte[]){
return ((byte[])value).Length == 0;
}
return false;
}
#endif
/// <summary>
/// Join the given strings by a delimiter.
/// </summary>
public static string Join(this string[] values, string delimiter) {
return string.Join(delimiter, values);
}
/// <summary>
/// Join the given strings by a delimiter.
/// </summary>
public static string Join(this List<string> values, string delimiter) {
#if NET20 || NET35
return string.Join(delimiter, values.ToArray());
#else
return string.Join(delimiter, values);
#endif
}
/// <summary>
/// Adds a prefix to the given strings, returns a new array.
/// </summary>
public static string[] AddPrefix(this string[] values, string prefix, bool trim = false) {
List<string> results = new List<string>();
foreach (string v in values) {
string txt = prefix + (trim ? v.Trim() : v);
results.Add(txt);
}
return results.ToArray();
}
/// <summary>
/// Adds a prefix to the given strings, returns a new array.
/// </summary>
public static List<string> AddPrefix(this List<string> values, string prefix, bool trim = false) {
List<string> results = new List<string>();
foreach (string v in values) {
string txt = prefix + (trim ? v.Trim() : v);
results.Add(txt);
}
return results;
}
/// <summary>
/// Adds a prefix to the given strings, returns a new array.
/// </summary>
public static List<string> ItemsToString(this object[] args) {
List<string> results = new List<string>();
if (args == null) {
return results;
}
foreach (object v in args) {
string txt;
if (v == null){
txt = "null";
} else if (v is string) {
txt = ("\"" + v as string + "\"");
} else {
txt = v.ToString();
}
results.Add(txt);
}
return results;
}
#if NET20 || NET35
public static bool HasFlag(this FtpHashAlgorithm flags, FtpHashAlgorithm flag) {
return (flags & flag) == flag;
}
public static bool HasFlag(this FtpCapability flags, FtpCapability flag) {
return (flags & flag) == flag;
}
public static bool HasFlag(this FtpVerify flags, FtpVerify flag) {
return (flags & flag) == flag;
}
public static bool HasFlag(this FtpError flags, FtpError flag) {
return (flags & flag) == flag;
}
public static void Restart(this Stopwatch watch) {
watch.Stop();
watch.Start();
}
#endif
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Text;
#if !CORE
using System.Reflection;
using System.Linq;
#endif
namespace FluentFTP {
internal static class FtpReflection {
#if !CORE
public static object GetField(this object obj, string fieldName) {
var tp = obj.GetType();
var info = GetAllFields(tp).Where(f => f.Name == fieldName).Single();
return info.GetValue(obj);
}
public static void SetField(this object obj, string fieldName, object value) {
var tp = obj.GetType();
var info = GetAllFields(tp).Where(f => f.Name == fieldName).Single();
info.SetValue(obj, value);
}
public static object GetStaticField(this Assembly assembly, string typeName, string fieldName) {
var tp = assembly.GetType(typeName);
var info = GetAllFields(tp).Where(f => f.IsStatic).Where(f => f.Name == fieldName).Single();
return info.GetValue(null);
}
public static object GetProperty(this object obj, string propertyName) {
var tp = obj.GetType();
var info = GetAllProperties(tp).Where(f => f.Name == propertyName).Single();
return info.GetValue(obj, null);
}
public static object CallMethod(this object obj, string methodName, params object[] prm) {
var tp = obj.GetType();
var info = GetAllMethods(tp).Where(f => f.Name == methodName && f.GetParameters().Length == prm.Length).Single();
object rez = info.Invoke(obj, prm);
return rez;
}
public static object NewInstance(this Assembly assembly, string typeName, params object[] prm) {
var tp = assembly.GetType(typeName);
var info = tp.GetConstructors().Where(f => f.GetParameters().Length == prm.Length).Single();
object rez = info.Invoke(prm);
return rez;
}
public static object InvokeStaticMethod(this Assembly assembly, string typeName, string methodName, params object[] prm) {
var tp = assembly.GetType(typeName);
var info = GetAllMethods(tp).Where(f => f.IsStatic).Where(f => f.Name == methodName && f.GetParameters().Length == prm.Length).Single();
object rez = info.Invoke(null, prm);
return rez;
}
public static object GetEnumValue(this Assembly assembly, string typeName, int value) {
var tp = assembly.GetType(typeName);
object rez = Enum.ToObject(tp, value);
return rez;
}
private static IEnumerable<FieldInfo> GetAllFields(Type t) {
if (t == null)
return Enumerable.Empty<FieldInfo>();
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly;
return t.GetFields(flags).Concat(GetAllFields(t.BaseType));
}
private static IEnumerable<PropertyInfo> GetAllProperties(Type t) {
if (t == null)
return Enumerable.Empty<PropertyInfo>();
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly;
return t.GetProperties(flags).Concat(GetAllProperties(t.BaseType));
}
private static IEnumerable<MethodInfo> GetAllMethods(Type t) {
if (t == null)
return Enumerable.Empty<MethodInfo>();
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly;
return t.GetMethods(flags).Concat(GetAllMethods(t.BaseType));
}
#endif
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -35,19 +36,21 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG;ASYNC;NET45</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>TRACE;ASYNC;NET45</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>3313DBEFFBEC4C1DB9EB9C32C7AE16B7932B4A65</ManifestCertificateThumbprint>
@@ -85,6 +88,7 @@
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -97,6 +101,34 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileTransfer.cs" />
<Compile Include="FluentFTP\Client\FtpClient_Connection.cs" />
<Compile Include="FluentFTP\Client\FtpClient_Hash.cs" />
<Compile Include="FluentFTP\Client\FtpClient_HighLevel.cs" />
<Compile Include="FluentFTP\Client\FtpClient_Listing.cs" />
<Compile Include="FluentFTP\Client\FtpClient_LowLevel.cs" />
<Compile Include="FluentFTP\Client\FtpClient_Management.cs" />
<Compile Include="FluentFTP\Client\IFtpClient.cs" />
<Compile Include="FluentFTP\Helpers\FtpEnums.cs" />
<Compile Include="FluentFTP\Helpers\FtpEvents.cs" />
<Compile Include="FluentFTP\Helpers\FtpExceptions.cs" />
<Compile Include="FluentFTP\Helpers\FtpHash.cs" />
<Compile Include="FluentFTP\Helpers\FtpListItem.cs" />
<Compile Include="FluentFTP\Helpers\FtpListParser.cs" />
<Compile Include="FluentFTP\Helpers\FtpReply.cs" />
<Compile Include="FluentFTP\Helpers\FtpTrace.cs" />
<Compile Include="FluentFTP\Helpers\IntRef.cs" />
<Compile Include="FluentFTP\Proxy\FtpClientHttp11Proxy.cs" />
<Compile Include="FluentFTP\Proxy\FtpClientProxy.cs" />
<Compile Include="FluentFTP\Proxy\FtpClientUserAtHostProxy.cs" />
<Compile Include="FluentFTP\Proxy\FtpClientUserAtHostProxyBlueCoat.cs" />
<Compile Include="FluentFTP\Proxy\ProxyInfo.cs" />
<Compile Include="FluentFTP\Stream\FtpDataStream.cs" />
<Compile Include="FluentFTP\Stream\FtpSocketStream.cs" />
<Compile Include="FluentFTP\Stream\FtpSslStream.cs" />
<Compile Include="FluentFTP\Utils\FtpExtensions.cs" />
<Compile Include="FluentFTP\Utils\FtpReflection.cs" />
<Compile Include="FluentFTP\Utils\NET2Compatibility.cs" />
<Compile Include="ListViewNF.cs">
<SubType>Component</SubType>
</Compile>
@@ -144,6 +176,7 @@
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="app.manifest" />
<None Include="packages.config" />

View File

@@ -9,6 +9,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -882,10 +883,19 @@ namespace NewsCrawler
return;
}
if(m_bBuy == true)
if (m_bBuy == true)
{
FileTransfer ft = new FileTransfer();
string today = DateTime.Now.ToString("yyyy-MM-dd");
string macAddr = NetworkInterface.GetAllNetworkInterfaces()[0].GetPhysicalAddress().ToString();
ft.SendDir("/configure", macAddr + "/NewsCrawler/" + today);
Util.Log(Util.LOG_TYPE.VERVOSE, "매수 시작");
}
else
{
Util.Log(Util.LOG_TYPE.VERVOSE, "매수 취소");
}
}
public string[] GetAccounts()

View File

@@ -8,64 +8,56 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace NewsCrawler.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if((resourceMan==null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NewsCrawler.Properties.Resources", typeof(Resources).Assembly);
resourceMan=temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture=value;
}
}
}
namespace NewsCrawler.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NewsCrawler.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -8,23 +8,19 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace NewsCrawler.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
namespace NewsCrawler.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -225,6 +225,10 @@ namespace NewsCrawler
if(result != null)
return new RESULT(TYPE.NEGATIVE, result.ToString());
result = m_PositiveForce.Find(s => s.IsMatch(strText));
if (result != null)
return new RESULT(TYPE.POSITIVE_FORCE, result.ToString());
result = m_Manual.Find(s => s.IsMatch(strText));
if(result != null)
return new RESULT(TYPE.MANUAL, result.ToString());
@@ -233,10 +237,6 @@ namespace NewsCrawler
if(result != null)
return new RESULT(TYPE.POSITIVE, result.ToString());
result = m_PositiveForce.Find(s => s.IsMatch(strText));
if (result != null)
return new RESULT(TYPE.POSITIVE_FORCE, result.ToString());
return new RESULT(TYPE.NOT_MATCHED, "");
}