1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com) 5 * 6 * This software is provided 'as-is', without any express or implied warranty. 7 * In no event will the authors be held liable for any damages arising from the 8 * use of this software. 9 * 10 * Permission is granted to anyone to use this software for any purpose, 11 * including commercial applications, and to alter it and redistribute it 12 * freely, subject to the following restrictions: 13 * 14 * 1. The origin of this software must not be misrepresented; you must not claim 15 * that you wrote the original software. If you use this software in a product, 16 * an acknowledgment in the product documentation would be appreciated but is 17 * not required. 18 * 19 * 2. Altered source versions must be plainly marked as such, and must not be 20 * misrepresented as being the original software. 21 * 22 * 3. This notice may not be removed or altered from any source distribution 23 * 24 * 25 * DSFML is based on SFML (Copyright Laurent Gomila) 26 */ 27 28 /** 29 * The $(U Ftp) class is a very simple FTP client that allows you to communicate with 30 * an FTP server. The FTP protocol allows you to manipulate a remote file system 31 * (list files, upload, download, create, remove, ...). 32 * 33 * Using the FTP client consists of 4 parts: 34 * $(UL 35 * $(LI Connecting to the FTP server) 36 * $(LI Logging in (either as a registered user or anonymously)) 37 * $(LI Sending commands to the server) 38 * $(LI Disconnecting (this part can be done implicitly by the destructor))) 39 * 40 * $(PARA 41 * Every command returns a FTP response, which contains the status code as well 42 * as a message from the server. Some commands such as `getWorkingDirectory()` 43 * and `getDirectoryListing()` return additional data, and use a class derived 44 * from `Ftp.Response` to provide this data. The most often used commands are 45 * directly provided as member functions, but it is also possible to use 46 * specific commands with the `sendCommand()` function. 47 * 48 * Note that response statuses >= 1000 are not part of the FTP standard, 49 * they are generated by SFML when an internal error occurs. 50 * 51 * All commands, especially upload and download, may take some time to complete. 52 * This is important to know if you don't want to block your application while 53 * the server is completing the task.) 54 * 55 * Example: 56 * --- 57 * // Create a new FTP client 58 * auto ftp = new Ftp(); 59 * 60 * // Connect to the server 61 * auto response = ftp.connect("ftp://ftp.myserver.com"); 62 * if (response.isOk()) 63 * writeln("Connected"); 64 * 65 * // Log in 66 * response = ftp.login("laurent", "dF6Zm89D"); 67 * if (response.isOk()) 68 * writeln("Logged in"); 69 * 70 * // Print the working directory 71 * auto directory = ftp.getWorkingDirectory(); 72 * if (directory.isOk()) 73 * writeln("Working directory: ", directory.getDirectory()); 74 * 75 * // Create a new directory 76 * response = ftp.createDirectory("files"); 77 * if (response.isOk()) 78 * writeln("Created new directory"); 79 * 80 * // Upload a file to this new directory 81 * response = ftp.upload("local-path/file.txt", "files", sf::Ftp::Ascii); 82 * if (response.isOk()) 83 * writeln("File uploaded"); 84 * 85 * // Send specific commands (here: FEAT to list supported FTP features) 86 * response = ftp.sendCommand("FEAT"); 87 * if (response.isOk()) 88 * writeln("Feature list:\n", response.getMessage()); 89 * 90 * // Disconnect from the server (optional) 91 * ftp.disconnect(); 92 * --- 93 */ 94 module nudsfml.network.ftp; 95 96 public import nudsfml.system.time; 97 98 import nudsfml.network.ipaddress; 99 100 /** 101 * An FTP client. 102 */ 103 class Ftp 104 { 105 /// Enumeration of transfer modes. 106 enum TransferMode 107 { 108 /// Binary mode (file is transfered as a sequence of bytes) 109 Binary, 110 /// Text mode using ASCII encoding. 111 Ascii, 112 /// Text mode using EBCDIC encoding. 113 Ebcdic, 114 } 115 116 package sfFtp* sfPtr; 117 118 /// Default Constructor. 119 this() 120 { 121 sfPtr = sfFtp_create(); 122 } 123 124 /// Destructor. 125 ~this() 126 { 127 import nudsfml.system.config; 128 mixin(destructorOutput); 129 sfFtp_destroy(sfPtr); 130 } 131 132 133 /** 134 * Get the current working directory. 135 * 136 * The working directory is the root path for subsequent operations 137 * involving directories and/or filenames. 138 * 139 * Returns: Server response to the request. 140 */ 141 DirectoryResponse getWorkingDirectory() 142 { 143 return new DirectoryResponse(sfFtp_getWorkingDirectory(sfPtr)); 144 } 145 146 /** 147 * Get the contents of the given directory. 148 * 149 * This function retrieves the sub-directories and files contained in the 150 * given directory. It is not recursive. The directory parameter is relative 151 * to the current working directory. 152 * 153 * Params: 154 * directory = Directory to list 155 * 156 * Returns: Server response to the request. 157 */ 158 ListingResponse getDirectoryListing(const(char)[] directory = "") 159 { 160 import nudsfml.system.string; 161 return new ListingResponse(sfFtp_getDirectoryListing(sfPtr, directory.ptr, directory.length)); 162 } 163 164 /** 165 * Change the current working directory. 166 * 167 * The new directory must be relative to the current one. 168 * 169 * Params: 170 * directory = New working directory 171 * 172 * Returns: Server response to the request. 173 */ 174 Response changeDirectory(const(char)[] directory) 175 { 176 import nudsfml.system.string; 177 return new Response(sfFtp_changeDirectory(sfPtr, directory.ptr, 178 directory.length)); 179 } 180 181 /** 182 * Connect to the specified FTP server. 183 * 184 * The port has a default value of 21, which is the standard port used by 185 * the FTP protocol. You shouldn't use a different value, unless you really 186 * know what you do. 187 * 188 * This function tries to connect to the server so it may take a while to 189 * complete, especially if the server is not reachable. To avoid blocking 190 * your application for too long, you can use a timeout. The default value, 191 * Time.Zero, means that the system timeout will be used (which is 192 * usually pretty long). 193 * 194 * Params: 195 * address = Address of the FTP server to connect to 196 * port = Port used for the connection 197 * timeout = Maximum time to wait 198 * 199 * Returns: Server response to the request. 200 */ 201 Response connect(IpAddress address, ushort port = 21, Time timeout = Time.Zero) 202 { 203 return new Response(sfFtp_connect(sfPtr, &address, port, timeout.asMicroseconds())); 204 } 205 206 /** 207 * Connect to the specified FTP server. 208 * 209 * The port has a default value of 21, which is the standard port used by 210 * the FTP protocol. You shouldn't use a different value, unless you really 211 * know what you do. 212 * 213 * This function tries to connect to the server so it may take a while to 214 * complete, especially if the server is not reachable. To avoid blocking 215 * your application for too long, you can use a timeout. The default value, 216 * Time.Zero, means that the system timeout will be used (which is 217 * usually pretty long). 218 * 219 * Params: 220 * address = Name or ddress of the FTP server to connect to 221 * port = Port used for the connection 222 * timeout = Maximum time to wait 223 * 224 * Returns: Server response to the request. 225 */ 226 Response connect(const(char)[] address, ushort port = 21, Time timeout = Time.Zero) 227 { 228 auto iaddress = IpAddress(address); 229 return new Response(sfFtp_connect(sfPtr, &iaddress, port, timeout.asMicroseconds())); 230 } 231 232 /** 233 * Remove an existing directory. 234 * 235 * The directory to remove must be relative to the current working 236 * directory. Use this function with caution, the directory will be removed 237 * permanently! 238 * 239 * Params: 240 * name = Name of the directory to remove 241 * 242 * Returns: Server response to the request. 243 */ 244 Response deleteDirectory(const(char)[] name) 245 { 246 import nudsfml.system.string; 247 return new Response(sfFtp_deleteDirectory(sfPtr, name.ptr, name.length)); 248 } 249 250 /** 251 * Remove an existing file. 252 * 253 * The file name must be relative to the current working directory. Use this 254 * function with caution, the file will be removed permanently! 255 * 256 * Params: 257 * name = Name of the file to remove 258 * 259 * Returns: Server response to the request. 260 */ 261 Response deleteFile(const(char)[] name) 262 { 263 import nudsfml.system.string; 264 return new Response(sfFtp_deleteFile(sfPtr, name.ptr, name.length)); 265 } 266 267 /** 268 * Close the connection with the server. 269 * 270 * Returns: Server response to the request. 271 */ 272 Response disconnect() 273 { 274 import nudsfml.system.string; 275 return new Response(sfFtp_disconnect(sfPtr)); 276 } 277 278 /** 279 * Download a file from the server. 280 * 281 * The filename of the distant file is relative to the current working 282 * directory of the server, and the local destination path is relative to 283 * the current directory of your application. 284 * 285 * Params: 286 * remoteFile = Filename of the distant file to download 287 * localPath = Where to put to file on the local computer 288 * mode = Transfer mode 289 * 290 * Returns: Server response to the request. 291 */ 292 Response download(const(char)[] remoteFile, const(char)[] localPath, TransferMode mode = TransferMode.Binary) 293 { 294 import nudsfml.system.string; 295 return new Response(sfFtp_download(sfPtr, remoteFile.ptr, remoteFile.length, localPath.ptr, localPath.length ,mode)); 296 } 297 298 /** 299 * Send a null command to keep the connection alive. 300 * 301 * This command is useful because the server may close the connection 302 * automatically if no command is sent. 303 * 304 * Returns: Server response to the request. 305 */ 306 Response keepAlive() 307 { 308 return new Response(sfFtp_keepAlive(sfPtr)); 309 } 310 311 /** 312 * Log in using an anonymous account. 313 * 314 * Logging in is mandatory after connecting to the server. Users that are 315 * not logged in cannot perform any operation. 316 * 317 * Returns: Server response to the request. 318 */ 319 Response login() 320 { 321 return new Response(sfFtp_loginAnonymous(sfPtr)); 322 } 323 324 /** 325 * Log in using a username and a password. 326 * 327 * Logging in is mandatory after connecting to the server. Users that are 328 * not logged in cannot perform any operation. 329 * 330 * Params: 331 * name = User name 332 * password = The password 333 * 334 * Returns: Server response to the request. 335 */ 336 Response login(const(char)[] name, const(char)[] password) 337 { 338 import nudsfml.system.string; 339 return new Response(sfFtp_login(sfPtr, name.ptr, name.length, password.ptr, password.length)); 340 } 341 342 /** 343 * Go to the parent directory of the current one. 344 * 345 * Returns: Server response to the request. 346 */ 347 Response parentDirectory() 348 { 349 import nudsfml.system.string; 350 return new Response(sfFtp_parentDirectory(sfPtr)); 351 } 352 353 /** 354 * Create a new directory. 355 * 356 * The new directory is created as a child of the current working directory. 357 * 358 * Params: 359 * name = Name of the directory to create 360 * 361 * Returns: Server response to the request. 362 */ 363 Response createDirectory(const(char)[] name) 364 { 365 import nudsfml.system.string; 366 return new Response(sfFtp_createDirectory(sfPtr, name.ptr, name.length)); 367 } 368 369 /** 370 * Rename an existing file. 371 * 372 * The filenames must be relative to the current working directory. 373 * 374 * Params: 375 * file = File to rename 376 * newName = New name of the file 377 * 378 * Returns: Server response to the request. 379 */ 380 Response renameFile(const(char)[] file, const(char)[] newName) 381 { 382 import nudsfml.system.string; 383 return new Response(sfFtp_renameFile(sfPtr, file.ptr, file.length, newName.ptr, newName.length)); 384 } 385 386 /** 387 * Upload a file to the server. 388 * 389 * The name of the local file is relative to the current working directory 390 * of your application, and the remote path is relative to the current 391 * directory of the FTP server. 392 * 393 * Params: 394 * localFile = Path of the local file to upload 395 * remotePath = Where to put the file on the server 396 * mode = Transfer mode 397 * 398 * Returns: Server response to the request. 399 */ 400 Response upload(const(char)[] localFile, const(char)[] remotePath, TransferMode mode = TransferMode.Binary) 401 { 402 import nudsfml.system.string; 403 return new Response(sfFtp_upload(sfPtr, localFile.ptr, localFile.length, remotePath.ptr, remotePath.length, mode)); 404 } 405 406 /** 407 * Send a command to the FTP server. 408 * 409 * While the most often used commands are provided as member functions in 410 * the Ftp class, this method can be used to send any FTP command to the 411 * server. If the command requires one or more parameters, they can be 412 * specified in parameter. If the server returns information, you can 413 * extract it from the response using getMessage(). 414 * 415 * Params: 416 * command = Command to send 417 * parameter = Command parameter 418 * 419 * Returns: Server response to the request. 420 */ 421 Response sendCommand(const(char)[] command, const(char)[] parameter) { 422 import nudsfml.system.string; 423 return new Response(sfFtp_sendCommand(sfPtr, command.ptr, command.length, parameter.ptr, parameter.length)); 424 } 425 426 /// Specialization of FTP response returning a directory. 427 static class DirectoryResponse:Response 428 { 429 private string Directory; 430 431 //Internally used constructor 432 package this(sfFtpDirectoryResponse* FtpDirectoryResponce) 433 { 434 import nudsfml.system.string; 435 436 Directory =nudsfml.system..string.toString(sfFtpDirectoryResponse_getDirectory(FtpDirectoryResponce)); 437 438 super(sfFtpDirectoryResponse_getStatus(FtpDirectoryResponce), sfFtpDirectoryResponse_getMessage(FtpDirectoryResponce)); 439 440 sfFtpDirectoryResponse_destroy(FtpDirectoryResponce); 441 } 442 443 /** 444 * Get the directory returned in the response. 445 * 446 * Returns: Directory name. 447 */ 448 string getDirectory() const 449 { 450 return Directory; 451 } 452 } 453 454 /// Specialization of FTP response returning a filename lisiting. 455 static class ListingResponse:Response 456 { 457 private string[] Filenames; 458 459 //Internally used constructor 460 package this(sfFtpListingResponse* FtpListingResponce) 461 { 462 import nudsfml.system.string; 463 464 Filenames.length = sfFtpListingResponse_getCount(FtpListingResponce); 465 for(int i = 0; i < Filenames.length; i++) 466 { 467 Filenames[i] =nudsfml.system..string.toString(sfFtpListingResponse_getName(FtpListingResponce,i)); 468 } 469 470 super(sfFtpListingResponse_getStatus(FtpListingResponce), sfFtpListingResponse_getMessage(FtpListingResponce)); 471 472 sfFtpListingResponse_destroy(FtpListingResponce); 473 } 474 475 /** 476 * Return the array of directory/file names. 477 * 478 * Returns: Array containing the requested listing. 479 */ 480 const(string[]) getFilenames() const 481 { 482 return Filenames; 483 } 484 } 485 486 ///Define a FTP response. 487 static class Response 488 { 489 /// Status codes possibly returned by a FTP response. 490 enum Status 491 { 492 RestartMarkerReply = 110, 493 ServiceReadySoon = 120, 494 DataConnectionAlreadyOpened = 125, 495 OpeningDataConnection = 150, 496 497 Ok = 200, 498 PointlessCommand = 202, 499 SystemStatus = 211, 500 DirectoryStatus = 212, 501 FileStatus = 213, 502 HelpMessage = 214, 503 SystemType = 215, 504 ServiceReady = 220, 505 ClosingConnection = 221, 506 DataConnectionOpened = 225, 507 ClosingDataConnection = 226, 508 EnteringPassiveMode = 227, 509 LoggedIn = 230, 510 FileActionOk = 250, 511 DirectoryOk = 257, 512 513 NeedPassword = 331, 514 NeedAccountToLogIn = 332, 515 NeedInformation = 350, 516 ServiceUnavailable = 421, 517 DataConnectionUnavailable = 425, 518 TransferAborted = 426, 519 FileActionAborted = 450, 520 LocalError = 451, 521 InsufficientStorageSpace = 452, 522 523 CommandUnknown = 500, 524 ParametersUnknown = 501, 525 CommandNotImplemented = 502, 526 BadCommandSequence = 503, 527 ParameterNotImplemented = 504, 528 NotLoggedIn = 530, 529 NeedAccountToStore = 532, 530 FileUnavailable = 550, 531 PageTypeUnknown = 551, 532 NotEnoughMemory = 552, 533 FilenameNotAllowed = 553, 534 535 InvalidResponse = 1000, 536 ConnectionFailed = 1001, 537 ConnectionClosed = 1002, 538 InvalidFile = 1003, 539 } 540 541 private Status FtpStatus; 542 private string Message; 543 544 //Internally used constructor. 545 package this(sfFtpResponse* FtpResponce) 546 { 547 this(sfFtpResponse_getStatus(FtpResponce),sfFtpResponse_getMessage(FtpResponce)); 548 sfFtpResponse_destroy(FtpResponce); 549 } 550 551 //Internally used constructor. 552 package this(Ftp.Response.Status status = Ftp.Response.Status.InvalidResponse, const(char)* message = "") 553 { 554 import nudsfml.system.string; 555 FtpStatus = status; 556 Message =nudsfml.system..string.toString(message); 557 } 558 559 /** 560 * Get the full message contained in the response. 561 * 562 * Returns: The message. 563 */ 564 string getMessage() const 565 { 566 return Message; 567 } 568 569 /** 570 * Get the status code of the response. 571 * 572 * Returns: Status code. 573 */ 574 Status getStatus() const 575 { 576 return FtpStatus; 577 } 578 579 /** 580 * Check if the status code means a success. 581 * 582 * This function is defined for convenience, it is equivalent to testing 583 * if the status code is < 400. 584 * 585 * Returns: true if the status is a success, false if it is a failure. 586 */ 587 bool isOk() const 588 { 589 return FtpStatus< 400; 590 } 591 } 592 } 593 594 unittest 595 { 596 version(DSFML_Unittest_Network) 597 { 598 import std.stdio; 599 import nudsfml.system.err; 600 601 writeln("Unittest for Ftp"); 602 603 auto ftp = new Ftp(); 604 605 auto responce = ftp.connect("ftp.hq.nasa.gov");//Thanks, NASA! 606 607 if(responce.isOk()) 608 { 609 writeln("Connected! Huzzah!"); 610 } 611 else 612 { 613 writeln("Uh-oh"); 614 writeln(responce.getStatus()); 615 assert(0); 616 } 617 618 //annonymous log in 619 responce = ftp.login(); 620 if(responce.isOk()) 621 { 622 writeln("Logged in! Huzzah!"); 623 } 624 else 625 { 626 writeln("Uh-oh"); 627 writeln(responce.getStatus()); 628 assert(0); 629 } 630 631 auto directory = ftp.getWorkingDirectory(); 632 if (directory.isOk()) 633 { 634 writeln("Working directory: ", directory.getDirectory()); 635 } 636 637 auto listing = ftp.getDirectoryListing(); 638 639 if(listing.isOk()) 640 { 641 const(string[]) list = listing.getFilenames(); 642 643 size_t length; 644 645 if(list.length > 10) 646 { 647 length = 10; 648 } 649 else 650 { 651 length = list.length; 652 } 653 654 for(int i= 0; i < length; ++i) 655 { 656 writeln(list[i]); 657 } 658 } 659 660 writeln(); 661 } 662 } 663 664 private extern(C): 665 666 struct sfFtpDirectoryResponse; 667 struct sfFtpListingResponse; 668 struct sfFtpResponse; 669 struct sfFtp; 670 671 //FTP Listing Response Functions 672 673 ///Destroy a FTP listing response 674 void sfFtpListingResponse_destroy(sfFtpListingResponse* ftpListingResponse); 675 676 677 ///Get the status code of a FTP listing response 678 Ftp.Response.Status sfFtpListingResponse_getStatus(const sfFtpListingResponse* ftpListingResponse); 679 680 681 ///Get the full message contained in a FTP listing response 682 const(char)* sfFtpListingResponse_getMessage(const(sfFtpListingResponse)* ftpListingResponse); 683 684 ///Return the number of directory/file names contained in a FTP listing response 685 size_t sfFtpListingResponse_getCount(const(sfFtpListingResponse)* ftpListingResponse); 686 687 ///Return a directory/file name contained in a FTP listing response 688 const(char)* sfFtpListingResponse_getName(const(sfFtpListingResponse)* ftpListingResponse, size_t index); 689 690 //FTP Directory Responce Functions 691 692 ///Destroy a FTP directory response 693 void sfFtpDirectoryResponse_destroy(sfFtpDirectoryResponse* ftpDirectoryResponse); 694 695 ///Get the status code of a FTP directory response 696 Ftp.Response.Status sfFtpDirectoryResponse_getStatus(const(sfFtpDirectoryResponse)* ftpDirectoryResponse); 697 698 ///Get the full message contained in a FTP directory response 699 const(char)* sfFtpDirectoryResponse_getMessage(const(sfFtpDirectoryResponse)* ftpDirectoryResponse); 700 701 ///Get the directory returned in a FTP directory response 702 const(char)* sfFtpDirectoryResponse_getDirectory(const(sfFtpDirectoryResponse)* ftpDirectoryResponse); 703 704 //FTP Responce functions 705 706 ///Destroy a FTP response 707 void sfFtpResponse_destroy(sfFtpResponse* ftpResponse); 708 709 ///Get the status code of a FTP response 710 Ftp.Response.Status sfFtpResponse_getStatus(const(sfFtpResponse)* ftpResponse); 711 712 ///Get the full message contained in a FTP response 713 const (char)* sfFtpResponse_getMessage(const sfFtpResponse* ftpResponse); 714 715 ////FTP functions 716 717 ///Create a new Ftp object 718 sfFtp* sfFtp_create(); 719 720 ///Destroy a Ftp object 721 void sfFtp_destroy(sfFtp* ftp); 722 723 ///Connect to the specified FTP server 724 sfFtpResponse* sfFtp_connect(sfFtp* ftp, IpAddress* serverIP, ushort port, long timeout); 725 726 ///Log in using an anonymous account 727 sfFtpResponse* sfFtp_loginAnonymous(sfFtp* ftp); 728 729 ///Log in using a username and a password 730 sfFtpResponse* sfFtp_login(sfFtp* ftp, const(char)* userName, size_t userNameLength, const(char)* password, size_t passwordLength); 731 732 ///Close the connection with the server 733 sfFtpResponse* sfFtp_disconnect(sfFtp* ftp); 734 735 ///Send a null command to keep the connection alive 736 sfFtpResponse* sfFtp_keepAlive(sfFtp* ftp); 737 738 ///Get the current working directory 739 sfFtpDirectoryResponse* sfFtp_getWorkingDirectory(sfFtp* ftp); 740 741 ///Get the contents of the given directory 742 sfFtpListingResponse* sfFtp_getDirectoryListing(sfFtp* ftp, const(char)* directory, size_t length); 743 744 ///Change the current working directory 745 sfFtpResponse* sfFtp_changeDirectory(sfFtp* ftp, const(char)* directory, size_t length); 746 747 ///Go to the parent directory of the current one 748 sfFtpResponse* sfFtp_parentDirectory(sfFtp* ftp); 749 750 ///Create a new directory 751 sfFtpResponse* sfFtp_createDirectory(sfFtp* ftp, const(char)* name, size_t length); 752 753 ///Remove an existing directory 754 sfFtpResponse* sfFtp_deleteDirectory(sfFtp* ftp, const(char)* name, size_t length); 755 756 ///Rename an existing file 757 sfFtpResponse* sfFtp_renameFile(sfFtp* ftp, const(char)* file, size_t fileLength, const(char)* newName, size_t newNameLength); 758 759 ///Remove an existing file 760 sfFtpResponse* sfFtp_deleteFile(sfFtp* ftp, const(char)* name, size_t length); 761 762 ///Download a file from a FTP server 763 sfFtpResponse* sfFtp_download(sfFtp* ftp, const(char)* distantFile, size_t distantFileLength, const(char)* destPath, size_t destPathLength, int mode); 764 765 ///Upload a file to a FTP server 766 sfFtpResponse* sfFtp_upload(sfFtp* ftp, const(char)* localFile, size_t localFileLength, const(char)* destPath, size_t destPathLength, int mode); 767 768 ///Send a command to a FTP server 769 sfFtpResponse* sfFtp_sendCommand(sfFtp* ftp, const(char)* command, size_t commandLength, const(char)* parameter, size_t parameterLength);