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);