using system;
using system.collections.generic;
using system.text;
using system.net;
using system.io;
using system.globalization;
using system.text.regularexpressions;
namespace webbaselib
{
/// <summary>
/// ftp處理操作類
/// 功能:
/// 下載下傳檔案
/// 上傳檔案
/// 上傳檔案的進度資訊
/// 下載下傳檔案的進度資訊
/// 删除檔案
/// 列出檔案
/// 列出目錄
/// 進入子目錄
/// 退出目前目錄傳回上一層目錄
/// 判斷遠端檔案是否存在
/// 删除遠端檔案
/// 建立目錄
/// 删除目錄
/// 檔案(目錄)改名
/// </summary>
/// <remarks>
/// 建立人:南瘋
/// 建立時間:2007年4月28日
/// </remarks>
#region 檔案資訊結構
public struct filestruct
public string flags;
public string owner;
public string group;
public bool isdirectory;
public datetime createtime;
public string name;
}
public enum fileliststyle
unixstyle,
windowsstyle,
unknown
#endregion
public class newftp
#region 屬性資訊
/// ftp請求對象
ftpwebrequest request = null;
/// ftp響應對象
ftpwebresponse response = null;
/// ftp伺服器位址
private uri _uri;
public uri uri
get
if( _directorypath == "/" )
return _uri;
else
string struri = _uri.tostring();
if( struri.endswith( "/" ) )
struri = struri.substring( 0, struri.length - 1 );
return new uri( struri + this.directorypath );
set
if( value.scheme != uri.urischemeftp )
throw new exception( "ftp 位址格式錯誤!" );
_uri = new uri( value.getleftpart( uripartial.authority ) );
_directorypath = value.absolutepath;
if( !_directorypath.endswith( "/" ) )
_directorypath += "/";
/// 目前工作目錄
private string _directorypath;
public string directorypath
return _directorypath;
_directorypath = value;
/// ftp登入使用者
private string _username;
public string username
return _username;
_username = value;
/// 錯誤資訊
private string _errormsg;
public string errormsg
return _errormsg;
_errormsg = value;
/// ftp登入密碼
private string _password;
public string password
return _password;
_password = value;
/// 連接配接ftp伺服器的代理服務
private webproxy _proxy = null;
public webproxy proxy
return _proxy;
_proxy = value;
/// 是否需要删除臨時檔案
private bool _isdeletetempfile = false;
/// 異步上傳所臨時生成的檔案
private string _uploadtempfile = "";
#region 事件
public delegate void de_downloadprogresschanged( object sender, downloadprogresschangedeventargs e );
public delegate void de_downloaddatacompleted( object sender, system.componentmodel.asynccompletedeventargs e );
public delegate void de_uploadprogresschanged( object sender, uploadprogresschangedeventargs e );
public delegate void de_uploadfilecompleted( object sender, uploadfilecompletedeventargs e );
/// 異步下載下傳進度發生改變觸發的事件
public event de_downloadprogresschanged downloadprogresschanged;
/// 異步下載下傳檔案完成之後觸發的事件
public event de_downloaddatacompleted downloaddatacompleted;
/// 異步上傳進度發生改變觸發的事件
public event de_uploadprogresschanged uploadprogresschanged;
/// 異步上傳檔案完成之後觸發的事件
public event de_uploadfilecompleted uploadfilecompleted;
#region 構造析構函數
/// 構造函數
/// <param name="ftpuri">ftp位址</param>
/// <param name="strusername">登入使用者名</param>
/// <param name="strpassword">登入密碼</param>
public newftp( uri ftpuri, string strusername, string strpassword )
this._uri = new uri( ftpuri.getleftpart( uripartial.authority ) );
_directorypath = ftpuri.absolutepath;
this._username = strusername;
this._password = strpassword;
this._proxy = null;
/// <param name="objproxy">連接配接代理</param>
public newftp( uri ftpuri, string strusername, string strpassword, webproxy objproxy )
this._proxy = objproxy;
public newftp()
this._username = "anonymous"; //匿名使用者
this._password = "@anonymous";
this._uri = null;
/// 析構函數
~newftp()
if( response != null )
response.close();
response = null;
if( request != null )
request.abort();
request = null;
#region 建立連接配接
/// 建立ftp連結,傳回響應對象
/// <param name="uri">ftp位址</param>
/// <param name="ftpmathod">操作指令</param>
private ftpwebresponse open( uri uri, string ftpmathod )
try
request = ( ftpwebrequest ) webrequest.create( uri );
request.method = ftpmathod;
request.usebinary = true;
request.credentials = new networkcredential( this.username, this.password );
if( this.proxy != null )
request.proxy = this.proxy;
return ( ftpwebresponse ) request.getresponse();
catch( exception ep )
errormsg = ep.tostring();
throw ep;
/// 建立ftp連結,傳回請求對象
private ftpwebrequest openrequest( uri uri, string ftpmathod )
return request;
#region 下載下傳檔案
/// 從ftp伺服器下載下傳檔案,使用與遠端檔案同名的檔案名來儲存檔案
/// <param name="remotefilename">遠端檔案名</param>
/// <param name="localpath">本地路徑</param>
public bool downloadfile( string remotefilename, string localpath )
return downloadfile( remotefilename, localpath, remotefilename );
/// 從ftp伺服器下載下傳檔案,指定本地路徑和本地檔案名
/// <param name="localfilepath">儲存檔案的本地路徑,後面帶有""</param>
/// <param name="localfilename">儲存本地的檔案名</param>
public bool downloadfile( string remotefilename, string localpath, string localfilename )
byte[] bt = null;
if( !isvalidfilechars( remotefilename ) || !isvalidfilechars( localfilename ) || !isvalidpathchars( localpath ) )
throw new exception( "非法檔案名或目錄名!" );
if( !directory.exists( localpath ) )
throw new exception( "本地檔案路徑不存在!" );
string localfullpath = path.combine( localpath, localfilename );
if( file.exists( localfullpath ) )
throw new exception( "目前路徑下已經存在同名檔案!" );
bt = downloadfile( remotefilename );
if( bt != null )
filestream stream = new filestream( localfullpath, filemode.create );
stream.write( bt, 0, bt.length );
stream.flush();
stream.close();
return true;
return false;
/// 從ftp伺服器下載下傳檔案,傳回檔案二進制資料
public byte[] downloadfile( string remotefilename )
if( !isvalidfilechars( remotefilename ) )
response = open( new uri( this.uri.tostring() + remotefilename ), webrequestmethods.ftp.downloadfile );
stream reader = response.getresponsestream();
memorystream mem = new memorystream( 1024 * 500 );
byte[] buffer = new byte[ 1024 ];
int bytesread = 0;
int totalbyteread = 0;
while( true )
bytesread = reader.read( buffer, 0, buffer.length );
totalbyteread += bytesread;
if( bytesread == 0 )
break;
mem.write( buffer, 0, bytesread );
if( mem.length > 0 )
return mem.toarray();
return null;
#region 異步下載下傳檔案
/// 從ftp伺服器異步下載下傳檔案,指定本地路徑和本地檔案名
/// <param name="localpath">儲存檔案的本地路徑,後面帶有""</param>
public void downloadfileasync( string remotefilename, string localpath, string localfilename )
downloadfileasync( remotefilename, localfullpath );
/// 從ftp伺服器異步下載下傳檔案,指定本地完整路徑檔案名
/// <param name="localfullpath">本地完整路徑檔案名</param>
public void downloadfileasync( string remotefilename, string localfullpath )
mywebclient client = new mywebclient();
client.downloadprogresschanged += new downloadprogresschangedeventhandler( client_downloadprogresschanged );
client.downloadfilecompleted += new system.componentmodel.asynccompletedeventhandler( client_downloadfilecompleted );
client.credentials = new networkcredential( this.username, this.password );
client.proxy = this.proxy;
client.downloadfileasync( new uri( this.uri.tostring() + remotefilename ), localfullpath );
/// <param name="sender">下載下傳對象</param>
/// <param name="e">資料資訊對象</param>
void client_downloadfilecompleted( object sender, system.componentmodel.asynccompletedeventargs e )
if( downloaddatacompleted != null )
downloaddatacompleted( sender, e );
/// <param name="e">進度資訊對象</param>
void client_downloadprogresschanged( object sender, downloadprogresschangedeventargs e )
if( downloadprogresschanged != null )
downloadprogresschanged( sender, e );
#region 上傳檔案
/// 上傳檔案到ftp伺服器
/// <param name="localfullpath">本地帶有完整路徑的檔案名</param>
public bool uploadfile( string localfullpath )
return uploadfile( localfullpath, path.getfilename( localfullpath ), false );
/// <param name="localfullpath">本地帶有完整路徑的檔案</param>
/// <param name="overwriteremotefile">是否覆寫遠端伺服器上面同名的檔案</param>
public bool uploadfile( string localfullpath, bool overwriteremotefile )
return uploadfile( localfullpath, path.getfilename( localfullpath ), overwriteremotefile );
/// <param name="remotefilename">要在ftp伺服器上面儲存檔案名</param>
public bool uploadfile( string localfullpath, string remotefilename )
return uploadfile( localfullpath, remotefilename, false );
public bool uploadfile( string localfullpath, string remotefilename, bool overwriteremotefile )
if( !isvalidfilechars( remotefilename ) || !isvalidfilechars( path.getfilename( localfullpath ) ) || !isvalidpathchars( path.getdirectoryname( localfullpath ) ) )
filestream stream = new filestream( localfullpath, filemode.open, fileaccess.read );
byte[] bt = new byte[ stream.length ];
stream.read( bt, 0, ( int32 ) stream.length ); //注意,因為int32的最大限制,最大上傳檔案隻能是大約2g多一點
return uploadfile( bt, remotefilename, overwriteremotefile );
throw new exception( "本地檔案不存在!" );
/// <param name="filebytes">上傳的二進制資料</param>
public bool uploadfile( byte[] filebytes, string remotefilename )
return uploadfile( filebytes, remotefilename, false );
/// <param name="filebytes">檔案二進制内容</param>
public bool uploadfile( byte[] filebytes, string remotefilename, bool overwriteremotefile )
throw new exception( "非法檔案名!" );
if( !overwriteremotefile && fileexist( remotefilename ) )
throw new exception( "ftp服務上面已經存在同名檔案!" );
response = open( new uri( this.uri.tostring() + remotefilename ), webrequestmethods.ftp.uploadfile );
stream requeststream = request.getrequeststream();
memorystream mem = new memorystream( filebytes );
int totalread = 0;
bytesread = mem.read( buffer, 0, buffer.length );
totalread += bytesread;
requeststream.write( buffer, 0, bytesread );
requeststream.close();
response = ( ftpwebresponse ) request.getresponse();
mem.close();
mem.dispose();
filebytes = null;
#region 異步上傳檔案
/// 異步上傳檔案到ftp伺服器
public void uploadfileasync( string localfullpath )
uploadfileasync( localfullpath, path.getfilename( localfullpath ), false );
public void uploadfileasync( string localfullpath, bool overwriteremotefile )
uploadfileasync( localfullpath, path.getfilename( localfullpath ), overwriteremotefile );
public void uploadfileasync( string localfullpath, string remotefilename )
uploadfileasync( localfullpath, remotefilename, false );
public void uploadfileasync( string localfullpath, string remotefilename, bool overwriteremotefile )
client.uploadprogresschanged += new uploadprogresschangedeventhandler( client_uploadprogresschanged );
client.uploadfilecompleted += new uploadfilecompletedeventhandler( client_uploadfilecompleted );
client.uploadfileasync( new uri( this.uri.tostring() + remotefilename ), localfullpath );
public void uploadfileasync( byte[] filebytes, string remotefilename )
uploadfileasync( filebytes, remotefilename, false );
public void uploadfileasync( byte[] filebytes, string remotefilename, bool overwriteremotefile )
string temppath = system.environment.getfolderpath( environment.specialfolder.templates );
if( !temppath.endswith( "/" ) )
temppath += "/";
string tempfile = temppath + path.getrandomfilename();
tempfile = path.changeextension( tempfile, path.getextension( remotefilename ) );
filestream stream = new filestream( tempfile, filemode.createnew, fileaccess.write );
stream.write( filebytes, 0, filebytes.length ); //注意,因為int32的最大限制,最大上傳檔案隻能是大約2g多一點
stream.dispose();
_isdeletetempfile = true;
_uploadtempfile = tempfile;
uploadfileasync( tempfile, remotefilename, overwriteremotefile );
void client_uploadfilecompleted( object sender, uploadfilecompletedeventargs e )
if( _isdeletetempfile )
if( file.exists( _uploadtempfile ) )
file.setattributes( _uploadtempfile, fileattributes.normal );
file.delete( _uploadtempfile );
_isdeletetempfile = false;
if( uploadfilecompleted != null )
uploadfilecompleted( sender, e );
void client_uploadprogresschanged( object sender, uploadprogresschangedeventargs e )
if( uploadprogresschanged != null )
uploadprogresschanged( sender, e );
#region 列出目錄檔案資訊
/// 列出ftp伺服器上面目前目錄的所有檔案和目錄
public filestruct[] listfilesanddirectories()
response = open( this.uri, webrequestmethods.ftp.listdirectorydetails );
streamreader stream = new streamreader( response.getresponsestream(), encoding.default );
string datastring = stream.readtoend();
filestruct[] list = getlist( datastring );
return list;
/// 列出ftp伺服器上面目前目錄的所有檔案
public filestruct[] listfiles()
filestruct[] listall = listfilesanddirectories();
list<filestruct> listfile = new list<filestruct>();
foreach( filestruct file in listall )
if( !file.isdirectory )
listfile.add( file );
return listfile.toarray();
/// 列出ftp伺服器上面目前目錄的所有的目錄
public filestruct[] listdirectories()
list<filestruct> listdirectory = new list<filestruct>();
if( file.isdirectory )
listdirectory.add( file );
return listdirectory.toarray();
/// 獲得檔案和目錄清單
/// <param name="datastring">ftp傳回的清單字元資訊</param>
private filestruct[] getlist( string datastring )
list<filestruct> mylistarray = new list<filestruct>();
string[] datarecords = datastring.split( '
' );
fileliststyle _directoryliststyle = guessfileliststyle( datarecords );
foreach( string s in datarecords )
if( _directoryliststyle != fileliststyle.unknown && s != "" )
filestruct f = new filestruct();
f.name = "..";
switch( _directoryliststyle )
case fileliststyle.unixstyle:
f = parsefilestructfromunixstylerecord( s );
case fileliststyle.windowsstyle:
f = parsefilestructfromwindowsstylerecord( s );
if( !( f.name == "." || f.name == ".." ) )
mylistarray.add( f );
return mylistarray.toarray();
/// 從windows格式中傳回檔案資訊
/// <param name="record">檔案資訊</param>
private filestruct parsefilestructfromwindowsstylerecord( string record )
string processstr = record.trim();
string datestr = processstr.substring( 0, 8 );
processstr = ( processstr.substring( 8, processstr.length - 8 ) ).trim();
string timestr = processstr.substring( 0, 7 );
processstr = ( processstr.substring( 7, processstr.length - 7 ) ).trim();
datetimeformatinfo mydtfi = new cultureinfo( "en-us", false ).datetimeformat;
mydtfi.shorttimepattern = "t";
f.createtime = datetime.parse( datestr + " " + timestr, mydtfi );
if( processstr.substring( 0, 5 ) == "<dir>" )
f.isdirectory = true;
processstr = ( processstr.substring( 5, processstr.length - 5 ) ).trim();
string[] strs = processstr.split( new char[] { ' ' }, stringsplitoptions.removeemptyentries ); // true);
processstr = strs[ 1 ];
f.isdirectory = false;
f.name = processstr;
return f;
/// 判斷檔案清單的方式window方式還是unix方式
/// <param name="recordlist">檔案資訊清單</param>
private fileliststyle guessfileliststyle( string[] recordlist )
foreach( string s in recordlist )
if( s.length > 10
&& regex.ismatch( s.substring( 0, 10 ), "(-|d)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)" ) )
return fileliststyle.unixstyle;
else if( s.length > 8
&& regex.ismatch( s.substring( 0, 8 ), "[0-9][0-9]-[0-9][0-9]-[0-9][0-9]" ) )
return fileliststyle.windowsstyle;
return fileliststyle.unknown;
/// 從unix格式中傳回檔案資訊
private filestruct parsefilestructfromunixstylerecord( string record )
f.flags = processstr.substring( 0, 10 );
f.isdirectory = ( f.flags[ 0 ] == 'd' );
processstr = ( processstr.substring( 11 ) ).trim();
_cutsubstringfromstringwithtrim( ref processstr, ' ', 0 ); //跳過一部分
f.owner = _cutsubstringfromstringwithtrim( ref processstr, ' ', 0 );
f.group = _cutsubstringfromstringwithtrim( ref processstr, ' ', 0 );
string yearortime = processstr.split( new char[] { ' ' }, stringsplitoptions.removeemptyentries )[ 2 ];
if( yearortime.indexof( ":" ) >= 0 ) //time
processstr = processstr.replace( yearortime, datetime.now.year.tostring() );
f.createtime = datetime.parse( _cutsubstringfromstringwithtrim( ref processstr, ' ', 8 ) );
f.name = processstr; //最後就是名稱
/// 按照一定的規則進行字元串截取
/// <param name="s">截取的字元串</param>
/// <param name="c">查找的字元</param>
/// <param name="startindex">查找的位置</param>
private string _cutsubstringfromstringwithtrim( ref string s, char c, int startindex )
int pos1 = s.indexof( c, startindex );
string retstring = s.substring( 0, pos1 );
s = ( s.substring( pos1 ) ).trim();
return retstring;
#region 目錄或檔案存在的判斷
/// 判斷目前目錄下指定的子目錄是否存在
/// <param name="remotedirectoryname">指定的目錄名</param>
public bool directoryexist( string remotedirectoryname )
if( !isvalidpathchars( remotedirectoryname ) )
throw new exception( "目錄名非法!" );
filestruct[] listdir = listdirectories();
foreach( filestruct dir in listdir )
if( dir.name == remotedirectoryname )
/// 判斷一個遠端檔案是否存在伺服器目前目錄下面
public bool fileexist( string remotefilename )
throw new exception( "檔案名非法!" );
filestruct[] listfile = listfiles();
foreach( filestruct file in listfile )
if( file.name == remotefilename )
#region 删除檔案
/// 從ftp伺服器上面删除一個檔案
public void deletefile( string remotefilename )
response = open( new uri( this.uri.tostring() + remotefilename ), webrequestmethods.ftp.deletefile );
#region 重命名檔案
/// 更改一個檔案的名稱或一個目錄的名稱
/// <param name="remotefilename">原始檔案或目錄名稱</param>
/// <param name="newfilename">新的檔案或目錄的名稱</param>
public bool rename( string remotefilename, string newfilename )
if( !isvalidfilechars( remotefilename ) || !isvalidfilechars( newfilename ) )
if( remotefilename == newfilename )
if( fileexist( remotefilename ) )
request = openrequest( new uri( this.uri.tostring() + remotefilename ), webrequestmethods.ftp.rename );
request.renameto = newfilename;
throw new exception( "檔案在伺服器上不存在!" );
#region 拷貝、移動檔案
/// 把目前目錄下面的一個檔案拷貝到伺服器上面另外的目錄中,注意,拷貝檔案之後,目前工作目錄還是檔案原來所在的目錄
/// <param name="remotefile">目前目錄下的檔案名</param>
/// <param name="directoryname">新目錄名稱。
/// 說明:如果新目錄是目前目錄的子目錄,則直接指定子目錄。如: subdirectory1/subdirectory2 ;
/// 如果新目錄不是目前目錄的子目錄,則必須從根目錄一級一級的指定。如: ./newdirectory/subdirectory1/subdirectory2
/// </param>
/// <returns></returns>
public bool copyfiletoanotherdirectory( string remotefile, string directoryname )
string currentworkdir = this.directorypath;
byte[] bt = downloadfile( remotefile );
gotodirectory( directoryname );
bool success = uploadfile( bt, remotefile, false );
this.directorypath = currentworkdir;
return success;
/// 把目前目錄下面的一個檔案移動到伺服器上面另外的目錄中,注意,移動檔案之後,目前工作目錄還是檔案原來所在的目錄
public bool movefiletoanotherdirectory( string remotefile, string directoryname )
if( directoryname == "" )
if( !directoryname.startswith( "/" ) )
directoryname = "/" + directoryname;
if( !directoryname.endswith( "/" ) )
directoryname += "/";
bool success = rename( remotefile, directoryname + remotefile );
#region 建立、删除子目錄
/// 在ftp伺服器上目前工作目錄建立一個子目錄
/// <param name="directoryname">子目錄名稱</param>
public bool makedirectory( string directoryname )
if( !isvalidpathchars( directoryname ) )
if( directoryexist( directoryname ) )
throw new exception( "伺服器上面已經存在同名的檔案名或目錄名!" );
string a = this.uri.tostring();
string b = directoryname;
response = open( new uri( this.uri.tostring() + directoryname ), webrequestmethods.ftp.makedirectory );
/// 從目前工作目錄中删除一個子目錄
public bool removedirectory( string directoryname )
if( !directoryexist( directoryname ) )
throw new exception( "伺服器上面不存在指定的檔案名或目錄名!" );
response = open( new uri( this.uri.tostring() + directoryname ), webrequestmethods.ftp.removedirectory );
#region 檔案、目錄名稱有效性判斷
/// 判斷目錄名中字元是否合法
/// <param name="directoryname">目錄名稱</param>
public bool isvalidpathchars( string directoryname )
char[] invalidpathchars = path.getinvalidpathchars();
char[] dirchar = directoryname.tochararray();
foreach( char c in dirchar )
if( array.binarysearch( invalidpathchars, c ) >= 0 )
/// 判斷檔案名中字元是否合法
/// <param name="filename">檔案名稱</param>
public bool isvalidfilechars( string filename )
char[] invalidfilechars = path.getinvalidfilenamechars();
char[] namechar = filename.tochararray();
foreach( char c in namechar )
if( array.binarysearch( invalidfilechars, c ) >= 0 )
#region 目錄切換操作
/// 進入一個目錄
/// <param name="directoryname">
/// 新目錄的名字。
public bool gotodirectory( string directoryname )
string currentworkpath = this.directorypath;
directoryname = directoryname.replace( "/", "/" );
string[] directorynames = directoryname.split( new char[] { '/' } );
if( directorynames[ 0 ] == "." )
this.directorypath = "/";
if( directorynames.length == 1 )
array.clear( directorynames, 0, 1 );
bool success = false;
foreach( string dir in directorynames )
if( dir != null )
success = enteronesubdirectory( dir );
if( !success )
this.directorypath = currentworkpath;
/// 從目前工作目錄進入一個子目錄
private bool enteronesubdirectory( string directoryname )
if( directoryname.indexof( "/" ) >= 0 || !isvalidpathchars( directoryname ) )
throw new exception( "目錄名非法!" );
if( directoryname.length > 0 && directoryexist( directoryname ) )
_directorypath += directoryname;
/// 從目前工作目錄往上一級目錄
public bool comeoutdirectory()
errormsg = "目前目錄已經是根目錄!";
throw new exception( "目前目錄已經是根目錄!" );
char[] sp = new char[ 1 ] { '/' };
string[] strdir = _directorypath.split( sp, stringsplitoptions.removeemptyentries );
if( strdir.length == 1 )
_directorypath = "/";
_directorypath = string.join( "/", strdir, 0, strdir.length - 1 );
#region 重載webclient,支援ftp進度
internal class mywebclient : webclient
protected override webrequest getwebrequest( uri address )
ftpwebrequest req = ( ftpwebrequest ) base.getwebrequest( address );
req.usepassive = false;
return req;