FtpClient.cs (22757B)
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Runtime.InteropServices.WindowsRuntime; 6 using System.Threading.Tasks; 7 using WinPhoneFtp.FtpService; 8 using Windows.Networking.Sockets; 9 using Windows.Storage.Streams; 10 using System.Windows.Threading; 11 12 namespace WinPhoneFtp.FtpService 13 { 14 public class FtpClient 15 { 16 String Username = String.Empty; 17 String Password = String.Empty; 18 TcpClientSocket FtpCommandSocket = null; 19 FtpCommand ftpCommand = FtpCommand.None; 20 FtpPassiveOperation ftpPassiveOperation = FtpPassiveOperation.None; 21 FtpFileOperationInfo ftpFileInfo = null; 22 StreamSocket FtpDataChannel = null; 23 List<byte> fileListingData = null; 24 String RemoteDirectory = String.Empty; 25 Logger logger = null; 26 Dispatcher UIDispatcher = null; 27 28 public event EventHandler FtpConnected; 29 public event EventHandler FtpAuthenticationSucceeded; 30 public event EventHandler FtpAuthenticationFailed; 31 public event EventHandler<FtpDirectoryChangedEventArgs> FtpDirectoryChangedSucceded; 32 public event EventHandler<FtpDirectoryChangedEventArgs> FtpDirectoryChangedFailed; 33 public event EventHandler<FtpDisconnectedEventArgs> FtpDisconnected; 34 public event EventHandler<FtpPresentWorkingDirectoryEventArgs> FtpPresentWorkingDirectoryReceived; 35 public event EventHandler<FtpFileTransferEventArgs> FtpFileUploadSucceeded; 36 public event EventHandler<FtpFileTransferFailedEventArgs> FtpFileUploadFailed; 37 public event EventHandler<FtpFileTransferEventArgs> FtpFileDownloadSucceeded; 38 public event EventHandler<FtpFileTransferFailedEventArgs> FtpFileDownloadFailed; 39 public event EventHandler<FtpDirectoryListedEventArgs> FtpDirectoryListed; 40 public event EventHandler<FtpFileTransferProgressedEventArgs> FtpFileTransferProgressed; 41 42 public FtpClient(String FtpServerIpAddress, Dispatcher UIDispatcher) 43 : this(FtpServerIpAddress, "21", UIDispatcher) 44 { 45 } 46 47 public FtpClient(String FtpServerIpAddress, String PortNumber, Dispatcher UIDispatcher) 48 { 49 this.UIDispatcher = UIDispatcher; 50 logger = Logger.GetDefault(UIDispatcher); 51 logger.AddLog(String.Format("FTP Server IP Address: {0} with port {1}", FtpServerIpAddress, PortNumber)); 52 FtpCommandSocket = new TcpClientSocket(FtpServerIpAddress, PortNumber, 512, "FTP Command Channel", UIDispatcher); 53 FtpCommandSocket.DataReceived += FtpClientSocket_DataReceived; 54 FtpCommandSocket.ErrorOccured += FtpClientSocket_ErrorOccured; 55 FtpCommandSocket.SocketClosed += FtpClientSocket_SocketClosed; 56 FtpCommandSocket.SocketConnected += FtpCommandSocket_SocketConnected; 57 IsConnected = false; 58 } 59 60 public Boolean IsConnected 61 { 62 get; 63 private set; 64 } 65 66 public Boolean IsBusy 67 { 68 get; 69 private set; 70 } 71 72 public async Task ConnectAsync() 73 { 74 if (!IsConnected) 75 { 76 logger.AddLog("FTP Command Channel Initailized"); 77 await FtpCommandSocket.PrepareSocketAsync(); 78 } 79 } 80 81 async void FtpClientSocket_DataReceived(object sender, DataReceivedEventArgs e) 82 { 83 String Response = System.Text.Encoding.UTF8.GetString(e.GetData(), 0, e.GetData().Length); 84 logger.AddLog(String.Format("FTPServer -> {0}", Response)); 85 switch (ftpPassiveOperation) 86 { 87 case FtpPassiveOperation.FileDownload: 88 ftpCommand = FtpCommand.None; 89 if (Response.StartsWith("150") || Response.StartsWith("125")) 90 { 91 IsBusy = true; 92 DataReader dataReader = new DataReader(FtpDataChannel.InputStream); 93 dataReader.InputStreamOptions = InputStreamOptions.Partial; 94 while (!(await dataReader.LoadAsync(32768)).Equals(0)) 95 { 96 IBuffer databuffer = dataReader.DetachBuffer(); 97 RaiseFtpFileTransferProgressedEvent(databuffer.Length, false); 98 await ftpFileInfo.LocalFileStream.WriteAsync(databuffer.ToArray(), 0, Convert.ToInt32(databuffer.Length)); 99 } 100 await ftpFileInfo.LocalFileStream.FlushAsync(); 101 dataReader.Dispose(); 102 dataReader = null; 103 //FtpDataChannel.Dispose(); 104 //FtpDataChannel = null; 105 RaiseFtpFileDownloadSucceededEvent(ftpFileInfo.LocalFileStream, ftpFileInfo.RemoteFile); 106 } 107 else if (Response.StartsWith("226")) 108 { 109 IsBusy = false; 110 ftpPassiveOperation = FtpPassiveOperation.None; 111 } 112 else 113 { 114 IsBusy = false; 115 ftpPassiveOperation = FtpPassiveOperation.None; 116 RaiseFtpFileDownloadFailedEvent(ftpFileInfo.LocalFileStream, ftpFileInfo.RemoteFile, FtpFileTransferFailureReason.FileDoesNotExist); 117 } 118 break; 119 120 case FtpPassiveOperation.FileUpload: 121 ftpCommand = FtpCommand.None; 122 if (Response.StartsWith("150") || Response.StartsWith("125")) 123 { 124 IsBusy = true; 125 DataWriter dataWriter = new DataWriter(FtpDataChannel.OutputStream); 126 byte[] data = new byte[32768]; 127 while (!(await ftpFileInfo.LocalFileStream.ReadAsync(data, 0, data.Length)).Equals(0)) 128 { 129 dataWriter.WriteBytes(data); 130 await dataWriter.StoreAsync(); 131 RaiseFtpFileTransferProgressedEvent(Convert.ToUInt32(data.Length), true); 132 } 133 await dataWriter.FlushAsync(); 134 dataWriter.Dispose(); 135 dataWriter = null; 136 FtpDataChannel.Dispose(); 137 FtpDataChannel = null; 138 } 139 else if (Response.StartsWith("226")) 140 { 141 IsBusy = false; 142 ftpPassiveOperation = FtpPassiveOperation.None; 143 RaiseFtpFileUploadSucceededEvent(ftpFileInfo.LocalFileStream, ftpFileInfo.RemoteFile); 144 ftpFileInfo = null; 145 } 146 else 147 { 148 IsBusy = false; 149 ftpPassiveOperation = FtpPassiveOperation.None; 150 RaiseFtpFileUploadFailedEvent(ftpFileInfo.LocalFileStream, ftpFileInfo.RemoteFile, FtpFileTransferFailureReason.FileDoesNotExist); 151 ftpFileInfo = null; 152 } 153 break; 154 155 case FtpPassiveOperation.ListDirectory: 156 ftpCommand = FtpCommand.None; 157 if (Response.StartsWith("150") || Response.StartsWith("125")) 158 { 159 IsBusy = true; 160 DataReader dataReader = new DataReader(FtpDataChannel.InputStream); 161 dataReader.InputStreamOptions = InputStreamOptions.Partial; 162 fileListingData = new List<byte>(); 163 while (!(await dataReader.LoadAsync(1024)).Equals(0)) 164 { 165 fileListingData.AddRange(dataReader.DetachBuffer().ToArray()); 166 } 167 dataReader.Dispose(); 168 dataReader = null; 169 FtpDataChannel.Dispose(); 170 FtpDataChannel = null; 171 String listingData = System.Text.Encoding.UTF8.GetString(fileListingData.ToArray(), 0, fileListingData.ToArray().Length); 172 String[] listings = listingData.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); 173 List<String> Filenames = new List<String>(); 174 List<String> Directories = new List<String>(); 175 foreach (String listing in listings) 176 { 177 if (listing.StartsWith("drwx") || listing.Contains("<DIR>")) 178 { 179 Directories.Add(listing.Split(new char[] { ' ' }).Last()); 180 } 181 else 182 { 183 Filenames.Add(listing.Split(new char[] { ' ' }).Last()); 184 } 185 } 186 RaiseFtpDirectoryListedEvent(Directories.ToArray(), Filenames.ToArray()); 187 } 188 else if (Response.StartsWith("226")) 189 { 190 IsBusy = false; 191 ftpFileInfo = null; 192 ftpPassiveOperation = FtpPassiveOperation.None; 193 } 194 break; 195 196 case FtpPassiveOperation.None: 197 switch (ftpCommand) 198 { 199 case FtpCommand.Username: 200 if (Response.StartsWith("501")) 201 { 202 IsBusy = false; 203 RaiseFtpAuthenticationFailedEvent(); 204 break; 205 } 206 this.ftpCommand = FtpCommand.Password; 207 logger.AddLog(String.Format("FTPClient -> PASS {0}\r\n", this.Password)); 208 await FtpCommandSocket.SendDataAsync(String.Format("PASS {0}\r\n", this.Password)); 209 break; 210 211 case FtpCommand.Password: 212 this.ftpCommand = FtpCommand.None; 213 IsBusy = false; 214 if (Response.Contains("530")) 215 { 216 RaiseFtpAuthenticationFailedEvent(); 217 } 218 else 219 { 220 RaiseFtpAuthenticationSucceededEvent(); 221 } 222 break; 223 224 case FtpCommand.ChangeWorkingDirectory: 225 IsBusy = false; 226 if (Response.StartsWith("550")) 227 { 228 RaiseFtpDirectoryChangedFailedEvent(this.RemoteDirectory); 229 } 230 else 231 { 232 RaiseFtpDirectoryChangedSuccededEvent(this.RemoteDirectory); 233 } 234 break; 235 236 case FtpCommand.PresentWorkingDirectory: 237 if (Response.StartsWith("257")) 238 { 239 IsBusy = false; 240 RaiseFtpPresentWorkingDirectoryReceivedEvent(Response.Split(new Char[] { ' ', '"' }, StringSplitOptions.RemoveEmptyEntries)[1]); 241 } 242 break; 243 244 case FtpCommand.Type: 245 ftpCommand = FtpCommand.Passive; 246 logger.AddLog("FTPClient -> PASV\r\n"); 247 await FtpCommandSocket.SendDataAsync("PASV\r\n"); 248 break; 249 250 case FtpCommand.Passive: 251 if (Response.StartsWith("227")) 252 { 253 await PrepareDataChannelAsync(Response); 254 if (ftpFileInfo != null) 255 { 256 if (ftpFileInfo.IsUpload) 257 { 258 ftpPassiveOperation = FtpPassiveOperation.FileUpload; 259 logger.AddLog(String.Format("FTPClient -> STOR {0}\r\n", ftpFileInfo.RemoteFile)); 260 await FtpCommandSocket.SendDataAsync(String.Format("STOR {0}\r\n", ftpFileInfo.RemoteFile)); 261 } 262 else 263 { 264 ftpPassiveOperation = FtpPassiveOperation.FileDownload; 265 logger.AddLog(String.Format("FTPClient -> RETR {0}\r\n", ftpFileInfo.RemoteFile)); 266 await FtpCommandSocket.SendDataAsync(String.Format("RETR {0}\r\n", ftpFileInfo.RemoteFile)); 267 } 268 } 269 else 270 { 271 fileListingData = new List<byte>(); 272 ftpPassiveOperation = FtpPassiveOperation.ListDirectory; 273 logger.AddLog("FTPClient -> LIST\r\n"); 274 await FtpCommandSocket.SendDataAsync("LIST\r\n"); 275 } 276 } 277 break; 278 279 case FtpCommand.Logout: 280 ftpCommand = FtpCommand.None; 281 break; 282 283 case FtpCommand.None: 284 break; 285 } 286 break; 287 } 288 289 } 290 291 void FtpCommandSocket_SocketConnected(object sender, EventArgs e) 292 { 293 IsConnected = true; 294 RaiseFtpConnectedEvent(); 295 } 296 297 void FtpClientSocket_ErrorOccured(object sender, ErrorOccuredEventArgs e) 298 { 299 logger.AddLog(e.ExceptionObject.Message); 300 IsConnected = false; 301 RaiseFtpDisconnectedEvent(FtpDisconnectReason.SocketError); 302 } 303 304 void FtpClientSocket_SocketClosed(object sender, SocketClosedEventArgs e) 305 { 306 IsConnected = false; 307 if (!ftpCommand.Equals(FtpCommand.Logout)) 308 { 309 RaiseFtpDisconnectedEvent(FtpDisconnectReason.SocketClosed); 310 } 311 else 312 { 313 RaiseFtpDisconnectedEvent(FtpDisconnectReason.QuitCommand); 314 } 315 } 316 317 //public async Task AuthenticateAsync() 318 //{ 319 // await AuthenticateAsync("anonymous", "m@m.com"); //TODO: test value only 320 //} 321 322 public async Task AuthenticateAsync(String Username, String Password) 323 { 324 ftpCommand = FtpCommand.Username; 325 this.Username = Username; 326 this.Password = Password; 327 logger.AddLog(String.Format("FTPClient -> USER {0}\r\n", Username)); 328 await FtpCommandSocket.SendDataAsync(String.Format("USER {0}\r\n", Username)); 329 } 330 331 public async Task ChangeWorkingDirectoryAsync(String RemoteDirectory) 332 { 333 if (!IsBusy) 334 { 335 this.RemoteDirectory = RemoteDirectory; 336 ftpCommand = FtpCommand.ChangeWorkingDirectory; 337 logger.AddLog(String.Format("FTPClient -> CWD {0}\r\n", RemoteDirectory)); 338 await FtpCommandSocket.SendDataAsync(String.Format("CWD {0}\r\n", RemoteDirectory)); 339 } 340 } 341 342 public async Task GetPresentWorkingDirectoryAsync() 343 { 344 if (!IsBusy) 345 { 346 ftpCommand = FtpCommand.PresentWorkingDirectory; 347 logger.AddLog("FTPClient -> PWD\r\n"); 348 await FtpCommandSocket.SendDataAsync("PWD\r\n"); 349 } 350 } 351 352 public async Task GetDirectoryListingAsync() 353 { 354 if (!IsBusy) 355 { 356 fileListingData = null; 357 ftpFileInfo = null; 358 IsBusy = true; 359 ftpCommand = FtpCommand.Passive; 360 logger.AddLog("FTPClient -> PASV\r\n"); 361 await FtpCommandSocket.SendDataAsync("PASV\r\n"); 362 } 363 } 364 365 public async Task UploadFileAsync(System.IO.Stream LocalFileStream, String RemoteFilename) 366 { 367 if (!IsBusy) 368 { 369 ftpFileInfo = null; 370 IsBusy = true; 371 ftpFileInfo = new FtpFileOperationInfo(LocalFileStream, RemoteFilename, true); 372 ftpCommand = FtpCommand.Type; 373 logger.AddLog("FTPClient -> TYPE I\r\n"); 374 await FtpCommandSocket.SendDataAsync("TYPE I\r\n"); 375 } 376 } 377 378 public async Task DownloadFileAsync(System.IO.Stream LocalFileStream, String RemoteFilename) 379 { 380 if (!IsBusy) 381 { 382 ftpFileInfo = null; 383 IsBusy = true; 384 ftpFileInfo = new FtpFileOperationInfo(LocalFileStream, RemoteFilename, false); 385 ftpCommand = FtpCommand.Type; 386 logger.AddLog("FTPClient -> TYPE I\r\n"); 387 await FtpCommandSocket.SendDataAsync("TYPE I\r\n"); 388 } 389 } 390 391 public async Task DisconnectAsync() 392 { 393 ftpCommand = FtpCommand.Logout; 394 logger.AddLog("FTPClient -> QUIT\r\n"); 395 await FtpCommandSocket.SendDataAsync("QUIT\r\n"); 396 } 397 398 private async Task PrepareDataChannelAsync(String ChannelInfo) 399 { 400 ChannelInfo = ChannelInfo.Remove(0, "227 Entering Passive Mode".Length); //TODO: test value only 401 //Configure the IP Address 402 String[] Splits = ChannelInfo.Substring(ChannelInfo.IndexOf("(") + 1, ChannelInfo.Length - ChannelInfo.IndexOf("(") - 5).Split(new char[] { ',', ' ', }, StringSplitOptions.RemoveEmptyEntries); 403 String Ipaddr = String.Join(".", Splits, 0, 4); 404 405 //Calculate the Data Port 406 Int32 port = Convert.ToInt32(Splits[4]); 407 port = ((port << 8) | Convert.ToInt32(Splits[5])); 408 logger.AddLog(String.Format("FTP Data Channel IPAddress: {0}, Port: {1}", Ipaddr, port)); 409 FtpDataChannel = new StreamSocket(); 410 await FtpDataChannel.ConnectAsync(new Windows.Networking.HostName(Ipaddr), port.ToString()); 411 logger.AddLog("FTP Data Channel connected"); 412 } 413 414 private void RaiseFtpAuthenticationSucceededEvent() 415 { 416 if (FtpAuthenticationSucceeded != null) 417 { 418 FtpAuthenticationSucceeded(this, EventArgs.Empty); 419 } 420 } 421 422 private void RaiseFtpAuthenticationFailedEvent() 423 { 424 if (FtpAuthenticationFailed != null) 425 { 426 FtpAuthenticationFailed(this, EventArgs.Empty); 427 } 428 } 429 430 private void RaiseFtpDirectoryChangedSuccededEvent(String RemoteDirectory) 431 { 432 if (FtpDirectoryChangedSucceded != null) 433 { 434 FtpDirectoryChangedSucceded(this, new FtpDirectoryChangedEventArgs(RemoteDirectory)); 435 } 436 } 437 438 private void RaiseFtpDirectoryChangedFailedEvent(String RemoteDirectory) 439 { 440 if (FtpDirectoryChangedFailed != null) 441 { 442 FtpDirectoryChangedFailed(this, new FtpDirectoryChangedEventArgs(RemoteDirectory)); 443 } 444 } 445 446 private void RaiseFtpPresentWorkingDirectoryReceivedEvent(String PresentWorkingDirectory) 447 { 448 if (FtpPresentWorkingDirectoryReceived != null) 449 { 450 FtpPresentWorkingDirectoryReceived(this, new FtpPresentWorkingDirectoryEventArgs(PresentWorkingDirectory)); 451 } 452 } 453 454 private void RaiseFtpFileUploadSucceededEvent(Stream LocalFileStream, String RemoteFile) 455 { 456 if (FtpFileUploadSucceeded != null) 457 { 458 FtpFileUploadSucceeded(this, new FtpFileTransferEventArgs(LocalFileStream, RemoteFile)); 459 } 460 } 461 462 private void RaiseFtpFileUploadFailedEvent(Stream LocalFileStream, String RemoteFile, FtpFileTransferFailureReason FileTransferFailReason) 463 { 464 if (FtpFileUploadFailed != null) 465 { 466 FtpFileUploadFailed(this, new FtpFileTransferFailedEventArgs(LocalFileStream, RemoteFile, FileTransferFailReason)); 467 } 468 } 469 470 private void RaiseFtpFileDownloadSucceededEvent(Stream LocalFileStream, String RemoteFile) 471 { 472 if (FtpFileDownloadSucceeded != null) 473 { 474 FtpFileDownloadSucceeded(this, new FtpFileTransferEventArgs(LocalFileStream, RemoteFile)); 475 } 476 } 477 478 private void RaiseFtpFileDownloadFailedEvent(Stream LocalFileStream, String RemoteFile, FtpFileTransferFailureReason FileTransferFailReason) 479 { 480 if (FtpFileDownloadFailed != null) 481 { 482 FtpFileDownloadFailed(this, new FtpFileTransferFailedEventArgs(LocalFileStream, RemoteFile, FileTransferFailReason)); 483 } 484 } 485 486 private void RaiseFtpConnectedEvent() 487 { 488 if (FtpConnected != null) 489 { 490 FtpConnected(this, EventArgs.Empty); 491 } 492 } 493 494 private void RaiseFtpDisconnectedEvent(FtpDisconnectReason DisconnectReason) 495 { 496 if (FtpDisconnected != null) 497 { 498 FtpDisconnected(this, new FtpDisconnectedEventArgs(DisconnectReason)); 499 } 500 } 501 502 private void RaiseFtpDirectoryListedEvent(String[] Directories, String[] Filenames) 503 { 504 if (FtpDirectoryListed != null) 505 { 506 FtpDirectoryListed(this, new FtpDirectoryListedEventArgs(Directories, Filenames)); 507 } 508 } 509 510 private void RaiseFtpFileTransferProgressedEvent(UInt32 BytesTransfered, Boolean IsUpload) 511 { 512 if (FtpFileTransferProgressed != null) 513 { 514 FtpFileTransferProgressed(this, new FtpFileTransferProgressedEventArgs(BytesTransfered, IsUpload)); 515 } 516 } 517 } 518 }