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