// SftpClient.cs
//
//  Copyright (C) 2008-2009 Christian Eide
//
//  Based partly on code from JCraft,Inc.
//  Copyright (c) 2002,2003,2004 ymnk, JCraft,Inc. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

using System;
using Mono.Unix;
using System.Collections.Generic;

namespace bareFTP.Protocol.Sftp
{
	
	public class SftpClient
	{
		private static  byte SSH_FXP_INIT=               1;
		private static  byte SSH_FXP_VERSION=            2;
		private static  byte SSH_FXP_OPEN=               3;
		private static  byte SSH_FXP_CLOSE=              4;
		private static  byte SSH_FXP_READ=               5;
		private static  byte SSH_FXP_WRITE=              6;
		//private static  byte SSH_FXP_LSTAT=              7;
		//private static  byte SSH_FXP_FSTAT=              8;
		private static  byte SSH_FXP_SETSTAT=            9;
		//private static  byte SSH_FXP_FSETSTAT=          10;
		private static  byte SSH_FXP_OPENDIR=           11;
		private static  byte SSH_FXP_READDIR=           12;
		private static  byte SSH_FXP_REMOVE=            13;
		private static  byte SSH_FXP_MKDIR=             14;
		private static  byte SSH_FXP_RMDIR=             15;
		private static  byte SSH_FXP_REALPATH=          16;
		private static  byte SSH_FXP_STAT=              17;
		private static  byte SSH_FXP_RENAME=            18;
		//private static  byte SSH_FXP_READLINK=          19;
		private static  byte SSH_FXP_STATUS=           101;
		private static  byte SSH_FXP_HANDLE=           102;
		private static  byte SSH_FXP_DATA=             103;
		//private static  byte SSH_FXP_NAME=             104;
		private static  byte SSH_FXP_ATTRS=            105;
		//private static  byte SSH_FXP_EXTENDED=         (byte)200;
		//private static  byte SSH_FXP_EXTENDED_REPLY=   (byte)201;

		// pflags
		private static  int SSH_FXF_READ=           0x00000001;
		private static  int SSH_FXF_WRITE=          0x00000002;
		//private static  int SSH_FXF_APPEND=         0x00000004;
		private static  int SSH_FXF_CREAT=          0x00000008;
		private static  int SSH_FXF_TRUNC=          0x00000010;
		//private static  int SSH_FXF_EXCL=           0x00000020;

		//private static  int SSH_FILEXFER_ATTR_SIZE=         0x00000001;
		//private static  int SSH_FILEXFER_ATTR_UIDGID=       0x00000002;
		//private static  int SSH_FILEXFER_ATTR_PERMISSIONS=  0x00000004;
		//private static  int SSH_FILEXFER_ATTR_ACMODTIME=    0x00000008;
		//private static  uint SSH_FILEXFER_ATTR_EXTENDED=     0x80000000;

		public static  int SSH_FX_OK=                            0;
		public static  int SSH_FX_EOF=                           1;
		public static  int SSH_FX_NO_SUCH_FILE=                  2;
		public static  int SSH_FX_PERMISSION_DENIED=             3;
		public static  int SSH_FX_FAILURE=                       4;
		public static  int SSH_FX_BAD_MESSAGE=                   5;
		public static  int SSH_FX_NO_CONNECTION=                 6;
		public static  int SSH_FX_CONNECTION_LOST=               7;
		public static  int SSH_FX_OP_UNSUPPORTED=                8;
		/*
		SSH_FX_OK
		Indicates successful completion of the operation.
		SSH_FX_EOF
		indicates end-of-file condition; for SSH_FX_READ it means that no
		more data is available in the file, and for SSH_FX_READDIR it
		indicates that no more files are contained in the directory.
		SSH_FX_NO_SUCH_FILE
		is returned when a reference is made to a file which should exist
		but doesn't.
		SSH_FX_PERMISSION_DENIED
		is returned when the authenticated user does not have sufficient
		permissions to perform the operation.
		SSH_FX_FAILURE
		is a generic catch-all error message; it should be returned if an
		error occurs for which there is no more specific error code
		defined.
		SSH_FX_BAD_MESSAGE
		may be returned if a badly formatted packet or protocol
		incompatibility is detected.
		SSH_FX_NO_CONNECTION
		is a pseudo-error which indicates that the client has no
		connection to the server (it can only be generated locally by the
		client, and MUST NOT be returned by servers).
		SSH_FX_CONNECTION_LOST
		is a pseudo-error which indicates that the connection to the
		server has been lost (it can only be generated locally by the
		client, and MUST NOT be returned by servers).
		SSH_FX_OP_UNSUPPORTED
		indicates that an attempt was made to perform an operation which
		is not supported for the server (it may be generated locally by
		the client if e.g.  the version number exchange indicates that a
		required feature is not supported by the server, or it may be
		returned by the server if the server does not implement an
		operation).
		*/
		
		private bareFTP.Protocol.Sftp.Buffer buf;
		private UnixStream consoleReader;
		private UnixStream dataStream;
		private System.IO.StreamWriter consoleWriter;
		private int seq = 0;
		private IDialogHost dhost;
		private bool isConnected = false;
		private string cwd = string.Empty;
		private int server_version = -1;
		public System.Threading.EventWaitHandle ewh;
		private bareFTP.SftpPty.PseudoTerminal pty;
		private bool abort = false;
		
		public SftpClient(IDialogHost dhost)
		{
			ewh = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset);
			buf = new bareFTP.Protocol.Sftp.Buffer();
			this.dhost = dhost;
		}
		
		
		public void Open(string hostname, string user, string password, int port)
		{
			Preferences.Config conf = new Preferences.Config("ssh");
			
			pty = bareFTP.SftpPty.PseudoTerminal.Open (null,
					       conf.SSHProgramPath,
					       new string [] {conf.SSHProgramPath, hostname, "-e", "none", "-l", user, "-p", port.ToString(), "-s", "sftp" },
					       null);
			
			consoleReader = new UnixStream (pty.FileDescriptor, false);
			consoleWriter = new System.IO.StreamWriter(consoleReader);
			dataStream = new UnixStream(pty.SlaveFileDescriptor, false);
			abort = false;
			sendInit();
			
			string terminalStr = string.Empty;
			bool firsttry = true;
			int paswdcount = 0;
			
			while(true)
			{
				int f = pty.GetReadyFileDescriptor(pty.FileDescriptor, pty.SlaveFileDescriptor, false);
				if(f == pty.FileDescriptor)
				{
					byte [] b = new byte [300];
					int n = consoleReader.Read (b, 0, b.Length);
					
					if(n > 0)
					{
						terminalStr = System.Text.Encoding.ASCII.GetString(b).Trim();
						
						if(terminalStr.ToLower().Contains("yes/no"))
						{
							
							bool ans = false;
							Gtk.Application.Invoke(delegate {	
								ans = dhost.DisplayDialogQuestion(ewh,terminalStr);
							});
							ewh.WaitOne();
							//terminalStr = string.Empty;
							
							if(!ans)
							{
								// TODO: Close connections etc.
								break;
							}
							else
							{
								if(terminalStr.Contains("Yes/No"))
									consoleWriter.Write("Yes\n");
								if(terminalStr.Contains("yes/no"))
									consoleWriter.Write("yes\n");
								consoleWriter.Flush();
								continue;
							}
						}
						else if(terminalStr.ToLower().Contains("password"))
						{
							if(!firsttry)
							{
								paswdcount++;
								if(paswdcount >= 3)
								{
									throw new FtpException("Login failed.");
								}
								
								paswdcount++;
								Gtk.Application.Invoke(delegate {	
									password = dhost.DisplayPasswordDialog(ewh);
								});
								ewh.WaitOne();
								terminalStr = string.Empty;
							}
							firsttry = false;
							consoleWriter.Write(password + "\n");
							consoleWriter.Flush();
						}
					}
					else if(n == 0)
					{
						continue;
					}
				}
				else if(f == pty.SlaveFileDescriptor)
				{
					server_version = getVersion();
					OnLogTextEmitted(new LogTextEmittedArgs(MessageType.Info, "Server version: " + server_version));
					isConnected = true;
					cwd = pwd();
					break;	
				}
			}
		}
		
		public bool IsConnected
		{
			get { return isConnected; }	
		}
		
		public void Abort()
		{
			abort = true;
		}
		
		public void Disconnect()
		{
			if(isConnected)
			{
				
				int _signal = 0;
				
				Mono.Unix.Native.Syscall.close(pty.SlaveFileDescriptor);
				Mono.Unix.Native.Syscall.close(pty.FileDescriptor);
				
				Mono.Unix.Native.Syscall.kill(pty.ChildPid, Mono.Unix.Native.Signum.SIGQUIT);
				Mono.Unix.Native.Syscall.waitpid(pty.ChildPid, out _signal, Mono.Unix.Native.WaitOptions.WUNTRACED);
				
				dataStream.Close();
				consoleReader.Close();
				consoleWriter.Close();
				
				pty.Dispose();
				pty = null;
				isConnected = false;
			}
		}
		
		private void sendInit()
		{
			buf.reset();
			
			buf.putByte(SSH_FXP_INIT);
			buf.putInt(3);
			Write();
		}
		
		private int getVersion()
		{
			byte[] size = new byte[4];
			dataStream.Read(size,0,4);
			Array.Copy(size,buf.buffer,4);
			int msgsize;
			
			msgsize = buf.getInt();
			dataStream.Read(buf.buffer, 4,msgsize);
			if(buf.getByte() != SSH_FXP_VERSION)
			{
				throw new Exception("Invalid ssh version response");
			}
			return buf.getInt();
		}
		
		public void chdir(string dir)
		{
			
			string path = remoteAbsolutePath(dir);
			byte[] sdir = System.Text.Encoding.UTF8.GetBytes(path);
			buf.reset();
			buf.putByte(SSH_FXP_REALPATH);
			buf.putInt(seq++);
			buf.putString(sdir);
			Write();
			
			Header _header=new Header();
			_header=header(buf, _header);
			
			buf.rewind();
			fill(buf.buffer, 0, _header.length);
			
			buf.getInt();
			byte[] str=buf.getString();
			if(str!=null && str[0]!='/')
			{
					str = System.Text.Encoding.UTF8.GetBytes((cwd + "/" + System.Text.Encoding.UTF8.GetString(str)));
			}
			
			str=buf.getString();         // logname
			buf.getInt();              // attrs

			string newpwd = System.Text.Encoding.UTF8.GetString(str);
			SftpATTRS attr=_stat(newpwd);
			
			if((attr.Flags & SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS) == 0)
			{
				throw new SftpException(SSH_FX_FAILURE, 
				                        "Can't change directory: " + path);
			}
			if(!attr.isDir())
			{
				throw new SftpException(SSH_FX_FAILURE, 
				                        "Can't change directory: " + path);
			}
			
			cwd = newpwd;
		}
		
		public string getCurrentDirectory()
		{
			return cwd;
		}
		
		private string pwd()
		{
			byte[] sdir = System.Text.Encoding.UTF8.GetBytes(".");
			buf.reset();
			buf.putByte(SSH_FXP_REALPATH);
			buf.putInt(seq++);
			buf.putString(sdir);
			Write();
			
			Header _header=new Header();
			_header=header(buf, _header);
			
			buf.rewind();
			fill(buf.buffer, 0, _header.length);
			buf.getInt();
			string str = System.Text.Encoding.UTF8.GetString(buf.getString());
			return str;
		}
		
		public void rename(string oldpath, string newpath)
		{
			//throws SftpException{
			if(server_version<2)
			{
				throw new SftpException(SSH_FX_FAILURE, 
				                        "The remote sshd is too old to support rename operation.");
			}
			try
			{
				oldpath=remoteAbsolutePath(oldpath);
				newpath=remoteAbsolutePath(newpath);

				List<string> v = glob_remote(oldpath);
				int vsize=v.Count;
				if(vsize != 1)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				oldpath=(string)(v[0]);

				v=glob_remote(newpath);
				vsize=v.Count;
				if(vsize>=2)
				{
					throw new SftpException(SSH_FX_FAILURE, v.ToString());
				}
				if(vsize==1)
				{
					newpath=(string)(v[0]);
				}
				else
				{  // vsize==0
					newpath=Util.unquote(newpath);
				}

				buf.reset();
				buf.putByte(SSH_FXP_RENAME);
				buf.putInt(seq++);
				buf.putString(System.Text.Encoding.UTF8.GetBytes(oldpath));
				buf.putString(System.Text.Encoding.UTF8.GetBytes(newpath));
				Write();
			
				Header _header=new Header();
				_header=header(buf, _header);
			
				buf.rewind();
				fill(buf.buffer, 0, _header.length);
				if(_header.type != SSH_FXP_STATUS)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}

				int i=buf.getInt();
				if(i==SSH_FX_OK) 
					return;
				throwStatusError(buf, i, "rename");
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public List<bareFTP.Protocol.RemoteFile> xdir()
		{
			List<bareFTP.Protocol.RemoteFile> v = new List<bareFTP.Protocol.RemoteFile>();
			byte[] b3 = System.Text.Encoding.UTF8.GetBytes(cwd);
			buf.reset();
			buf.putByte(SSH_FXP_OPENDIR);
			buf.putInt(seq++);
			buf.putString(b3);
			Write();
			
			Header _header=new Header();
			_header=header(buf, _header);
			
			buf.reset();
			dataStream.Read(buf.buffer, 0, _header.length);
			
			string handle = System.Text.Encoding.UTF8.GetString(buf.getString());
			
			b3 = System.Text.Encoding.UTF8.GetBytes(handle);
			
			while(true)
			{
				buf.reset();
				buf.putByte(SSH_FXP_READDIR);
				buf.putInt(seq++);
				buf.putString(b3);
				Write();
			
				_header=header(buf, _header);
				int length = _header.length;
				
				if(_header.type==SSH_FXP_STATUS)
				{ 
					buf.rewind();
					fill(buf.buffer, 0, length);
					int i=buf.getInt();
					if(i==SSH_FX_EOF)
						break;
					
					throwStatusError(buf, i, "xdir");
				}
				
				buf.rewind();
				
				fill(buf.buffer, 0, 4); length-=4;
				int count=buf.getInt();
				
				buf.reset();
				
				while(count > 0)
				{
					if(length>0)
					{
						buf.shift();
						int j=(buf.buffer.Length>(buf.index+length)) ? length : (buf.buffer.Length-buf.index);
						int i=fill(buf.buffer, buf.index, j);
						buf.index+=i;
						length-=i;
					}
					byte[] filename=buf.getString();
					buf.getString();
					SftpATTRS attrs = SftpATTRS.getATTR(buf);
					
					if(true)
					{
							bareFTP.Protocol.RemoteFile _rf = new bareFTP.Protocol.RemoteFile();
							_rf.Filename = System.Text.Encoding.UTF8.GetString(filename);
							_rf.Group = attrs.GId.ToString();
							_rf.Owner = attrs.UId.ToString();
							_rf.IsDir = attrs.isDir();
							_rf.IsLink = attrs.isLink();
							_rf.LastModified = attrs.Mtime;
							_rf.Size = attrs.Size;
							_rf.Permissions = attrs.getPermissionsString();
							v.Add(_rf);
					}
					//Console.WriteLine(System.Text.Encoding.UTF8.GetString(filename));				
					count--;
				}
			}
			
			// Close
			buf.reset();
			buf.putByte(SSH_FXP_CLOSE);
			buf.putInt(seq++);
			buf.putString(b3);
			Write();
			GetResult();
			return v;
		}
		
		public void myget(bareFTP.Protocol.XferFile src, bareFTP.Protocol.FileAction action, System.IO.FileStream dst) 
		{ 
			try
			{
				string _src = src.Path.FileNameRemoteAbs;
				byte[] b3 = System.Text.Encoding.UTF8.GetBytes(_src);
				buf.reset();
				buf.putByte(SSH_FXP_OPEN);
				buf.putInt(seq++);
				buf.putString(b3);
				buf.putInt(SSH_FXF_READ);
				buf.putInt(0);
				Write();
				
				Header _header=new Header();
				_header=header(buf, _header);
				int length=_header.length;
				int type=_header.type;
				
				buf.rewind();
				
				fill(buf.buffer, 0, length);

				if(type!=SSH_FXP_STATUS && type!=SSH_FXP_HANDLE)
				{
					throw new SftpException(SSH_FX_FAILURE, "Type is "+type);
				}

				if(type==SSH_FXP_STATUS)
				{
					int i=buf.getInt();
					//throw new Exception("Tjohei");
					throwStatusError(buf, i, "myget");
				}
				
				byte[] handle=buf.getString();         // filename

				long offset=0;
				if(action == FileAction.Resume)
				{
					offset += dst.Position;
					src.TransferedBytes = offset;
				}

				int request_len=0;
				src.Status = DownloadStatus.Downloading;
				
				while(true)
				{

					request_len=buf.buffer.Length-13;
					if(server_version==0){ request_len=1024; }
					buf.reset();
					buf.putByte(SSH_FXP_READ);
					buf.putInt(seq++);
					buf.putString(handle);
					buf.putLong(offset);
					buf.putInt(request_len);
					Write();
					
					//sendREAD(handle, offset, request_len);
					
					_header=header(buf, _header);
					length=_header.length;
					type=_header.type;
					
					int i;
					if(type==SSH_FXP_STATUS)
					{
						buf.rewind();
						fill(buf.buffer, 0, length);
						i=buf.getInt();   
						if(i==SSH_FX_EOF)
						{
							break;
						}
						throwStatusError(buf, i, "myget");
					}
					
					if(type!=SSH_FXP_DATA)
					{
						break;
					}

					buf.rewind();
					fill(buf.buffer, 0, 4); length-=4;
					i=buf.getInt();   // length of data 
					int foo=i;
					
					while(foo>0)
					{
						if(abort)
						{
							src.Status = DownloadStatus.Aborted;
							return;
						}
						int bar=foo;
						if(bar>buf.buffer.Length)
						{
							bar=buf.buffer.Length;
						}
						i = dataStream.Read(buf.buffer, 0, bar);
						//i=io.ins.Read(buf.buffer, 0, bar);
						if(i<0)
						{
							break;
						}
						int data_len=i;
						dst.Write(buf.buffer, 0, data_len);

						offset+=data_len;
						src.TransferedBytes += data_len;
						foo-=data_len;
					}
					//System.out.println("length: "+length);  // length should be 0
				}
			
				dst.Flush();

				// Close
				buf.reset();
				buf.putByte(SSH_FXP_CLOSE);
				buf.putInt(seq++);
				buf.putString(b3);
				Write();
				GetResult();
				src.Status = DownloadStatus.Finished;
			}
			catch(System.Exception e)
			{
				System.Console.WriteLine("Error2: " + e.ToString());
				System.Console.WriteLine("Error: " + e.ToString());
				//System.Console.WriteLine("Src: " + src.FileName);
				
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public void myput(bareFTP.Protocol.XferFile file, bareFTP.Protocol.FileAction action, System.IO.FileStream src)
		{
			try
			{
				string dst = file.Path.FileNameRemoteAbs;
				long skip=0;
				
				if(action == bareFTP.Protocol.FileAction.Resume || action == bareFTP.Protocol.FileAction.Append)
   				{
   					try
   					{
   						SftpATTRS attr= _stat(dst);
   						skip=attr.Size;
   					}
   					catch(System.Exception)
   					{
   						//System.err.println(eee);
   					}
   				}
   				if(action == bareFTP.Protocol.FileAction.Resume && skip>0)
				{
					src.Position = skip;
   					if(src.Position<skip)
   					{
   						throw new SftpException(SSH_FX_FAILURE, "failed to resume for "+dst);
   					}
   				}
				
				buf.reset();
				buf.putByte(SSH_FXP_OPEN);
				buf.putInt(seq++);
				buf.putString(System.Text.Encoding.UTF8.GetBytes(dst));
				
   				if(action == bareFTP.Protocol.FileAction.Overwrite)
					buf.putInt(SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC);
				else
					buf.putInt(SSH_FXF_WRITE|/*SSH_FXF_APPEND|*/SSH_FXF_CREAT);
					
				buf.putInt(0);
				Write();
				
				Header _header=new Header();
				_header=header(buf, _header);
				
				buf.rewind();
				fill(buf.buffer, 0, _header.length);

				if(_header.type != SSH_FXP_STATUS && _header.type != SSH_FXP_HANDLE)
				{
					throw new SftpException(SSH_FX_FAILURE, "invalid type=" + _header.type);
				}
				if(_header.type==SSH_FXP_STATUS)
				{
					int i=buf.getInt();
					//Console.WriteLine("StatusError");
					throwStatusError(buf, i, "myput");
				}
				
				byte[] handle = buf.getString();
				
				byte[] data = null;
				long offset=0;
				
				if(action == bareFTP.Protocol.FileAction.Resume || action == bareFTP.Protocol.FileAction.Append)
   				{
					offset+=skip;
				}
				
				long numbytestotransfer = file.Size - offset;
				int headersize = 17 + 4 + 4 + handle.Length;
				int datalength = buf.buffer.Length - headersize;
				
				while(numbytestotransfer > 0)
				{
					if(abort)
					{
						file.Status = DownloadStatus.Aborted;
						return;
					}
					file.Status = bareFTP.Protocol.DownloadStatus.Uploading;
					int ack = seq++;
					buf.reset();
					buf.putByte(SSH_FXP_WRITE);
					buf.putInt(ack);
					buf.putString(handle);
					buf.putLong(offset);
					//buf.putString(System.Text.Encoding.UTF8.GetBytes(dst));
					
					int nread;
					if(numbytestotransfer > datalength)
					{
						data = new byte[datalength];
						nread = src.Read(data, 0, datalength);
					}
					else
					{
						data = new byte[(int)numbytestotransfer];
						nread = src.Read(data, 0, (int)numbytestotransfer);
					}
					buf.putString(data);
					//buf.index += nread;
					numbytestotransfer -= nread;
					Write();
					offset+=nread;
					file.TransferedBytes = offset;
					GetWriteResult(ack);
				}
				
				buf.reset();
				buf.putByte(SSH_FXP_CLOSE);
				buf.putInt(seq++);
				buf.putString(System.Text.Encoding.UTF8.GetBytes(dst));
				Write();
				GetResult();
				file.Status = DownloadStatus.Finished;
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, e.ToString());
			}
		}
		
		public void mkdir(string path)
		{
			//throws SftpException{
			//System.Console.WriteLine(path);
			try
			{
				path=remoteAbsolutePath(path);
				//System.Console.WriteLine(path);
				
				buf.reset();
				buf.putByte(SSH_FXP_MKDIR);
				buf.putInt(seq++);
				buf.putString(System.Text.Encoding.UTF8.GetBytes(path));
				buf.putInt(0);
				Write();
				
				Header _header=new Header();      
				_header=header(buf, _header);
				
				buf.rewind();
				fill(buf.buffer, 0, _header.length);

				if(_header.type != SSH_FXP_STATUS)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}

				int i=buf.getInt();
				if(i==SSH_FX_OK) return;
				throwStatusError(buf, i, "mkdir");
			}
			catch(System.Exception e)
			{
				//System.Console.WriteLine("Bah.." + e.Message);
				//System.Console.WriteLine("Bah.." + e.StackTrace);
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public void rmdir(string path)
		{
			try
			{
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				//int vsize=v.Count;
				Header _header=new Header();

				foreach(string item in v)
				{
					
					buf.reset();
					buf.putByte(SSH_FXP_RMDIR);
					buf.putInt(seq++);
					buf.putString(System.Text.Encoding.UTF8.GetBytes(item));
					Write();
					
					_header=header(buf, _header);
					buf.rewind();
					fill(buf.buffer, 0, _header.length);

					if(_header.type != SSH_FXP_STATUS)
					{
						throw new SftpException(SSH_FX_FAILURE, "");
					}

					int i=buf.getInt();
					if(i!=SSH_FX_OK)
					{
						throwStatusError(buf, i, "rmdir");
					}
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public void chmod(int permissions, string path) 
		{ 
			try
			{
				path=remoteAbsolutePath(path);

				List<string> v = glob_remote(path);
				int vsize=v.Count;
				for(int j=0; j<vsize; j++)
				{
					path=(string)(v[j]);

					SftpATTRS attr=_stat(path);
					
					attr.setFLAGS(0);
					attr.setPERMISSIONS(permissions);
					_setStat(path, attr);
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		public void rm(string path)
		{
		
			try
			{
				path=remoteAbsolutePath(path);
				List<string> v = glob_remote(path);
				Header _header=new Header();

				foreach(string item in v)
				{
					buf.reset();
					buf.putByte(SSH_FXP_REMOVE);
					buf.putInt(seq++);
					buf.putString(System.Text.Encoding.UTF8.GetBytes(item));
					Write();
					
					_header=header(buf, _header);
					buf.rewind();
					fill(buf.buffer, 0, _header.length);

					if(_header.type != SSH_FXP_STATUS)
					{
						throw new SftpException(SSH_FX_FAILURE, "");
					}
					int i=buf.getInt();
					if(i != SSH_FX_OK)
					{
						throwStatusError(buf, i, "rm");
					}
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		private List<string> glob_remote(string _path)
		{
			
			List<string> v = new List<string>();
			v.Add(Util.unquote(_path)); 
			return v;
		}
		
		internal class Header
		{
			public int length;
			public int type;
			public int rid;
		}
		
		private void throwStatusError(Buffer buf, int i, string method) 
		{ 
			if(server_version>=3)
			{
				byte[] str=buf.getString();
				//byte[] tag=buf.getString();
				throw new SftpException(i, method + ": " + System.Text.Encoding.UTF8.GetString(str));
			}
			else
			{
				throw new SftpException(i, method + " failure");
			}
		}
		
		internal Header header(bareFTP.Protocol.Sftp.Buffer buf, Header header)
		{
			buf.rewind();
			fill(buf.buffer, 0, 9);
			header.length=buf.getInt()-5;
			header.type=buf.getByte() & 0xff;
			header.rid=buf.getInt();  
			return header;
		}
		
		private void Write()
		{
			buf.setMsgLength();
			dataStream.Write(buf.buffer, 0, buf.index);
			dataStream.Flush();
		}
		
		private void GetResult()
		{
			Header _header=new Header();
			_header=header(buf, _header);
			
			buf.rewind();
			fill(buf.buffer, 0, _header.length);
			if(_header.type == SSH_FXP_STATUS)
			{
				buf.getInt();
				//string str = System.Text.Encoding.UTF8.GetString(buf.getString());
				System.Text.Encoding.UTF8.GetString(buf.getString());
			}
		}
		
		private void GetWriteResult(int ack)
		{
			Header _header=new Header();
			_header=header(buf, _header);
			
			buf.rewind();
			fill(buf.buffer, 0, _header.length);
			if(ack != _header.rid)
				throw new Exception("Wrong ack");
			
			if(_header.type == SSH_FXP_STATUS)
			{
				buf.getInt();
				//string str = System.Text.Encoding.UTF8.GetString(buf.getString());
				System.Text.Encoding.UTF8.GetString(buf.getString());
			}
			
			
		}
		
		private SftpATTRS _stat(string path)
		{
			
			buf.reset();
			buf.putByte(SSH_FXP_STAT);
			buf.putInt(seq++);
			buf.putString(System.Text.Encoding.UTF8.GetBytes(path));
			Write();
			
			Header _header=new Header();
			_header=header(buf, _header);
			
			int length=_header.length;
			int type=_header.type;
			buf.rewind();
			fill(buf.buffer, 0, length);

			if(type!=SSH_FXP_ATTRS)
			{
				if(type==SSH_FXP_STATUS)
				{
					int i=buf.getInt();
					throwStatusError(buf, i, "_stat");
				}
				//throw new SftpException(SSH_FX_FAILURE, "");
			}
			SftpATTRS attr=SftpATTRS.getATTR(buf);
			return attr;
		}
		
		private void _setStat(string path, SftpATTRS attr)
		{
			try
			{
				buf.reset();
				buf.putByte(SSH_FXP_SETSTAT);
				buf.putInt(seq++);
				buf.putString(System.Text.Encoding.UTF8.GetBytes(path));
				attr.dump(buf);
				Write();
				
				Header _header=new Header();
				_header=header(buf, _header);
				buf.rewind();
				fill(buf.buffer, 0, _header.length);

				if(_header.type!=SSH_FXP_STATUS)
				{
					throw new SftpException(SSH_FX_FAILURE, "");
				}
				int i=buf.getInt();
				if(i!=SSH_FX_OK)
				{
					throwStatusError(buf, i, "_setStat");
				}
			}
			catch(System.Exception e)
			{
				if(e is SftpException) throw (SftpException)e;
				throw new SftpException(SSH_FX_FAILURE, "");
			}
		}
		
		private int fill(byte[] buf, int s, int len) 
		{
			int i=0;
			int foo=s;
			while(len>0)
			{
				i=dataStream.Read(buf, s, len);
				if(i<=0)
				{
					throw new System.IO.IOException("inputstream is closed");
					//return (s-foo)==0 ? i : s-foo;
				}
				s+=i;
				len-=i;
			}
			return s-foo;
		}
		
		private string remoteAbsolutePath(string path)
		{
			if(path.StartsWith("/")) return path;
			if(cwd.EndsWith("/")) return cwd+path;
			return cwd+"/"+path;
		}
		
		public event EventHandler LogTextEmitted;
		public virtual void OnLogTextEmitted(LogTextEmittedArgs e)
		{
			LogTextEmitted(this, e);
		}
		
	}
}
