1 /*
2 DSFML - The Simple and Fast Multimedia Library for D
3 
4 Copyright (c) 2013 - 2015 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 use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose, including commercial applications,
10 and to alter it and redistribute it freely, subject to the following restrictions:
11 
12 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
13 If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
14 
15 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
16 
17 3. This notice may not be removed or altered from any source distribution
18 */
19 
20 ///A module containing the Ftp class.
21 module dsfml.network.ftp;
22 
23 import core.time;
24 
25 import dsfml.network.ipaddress;
26 
27 
28 /**
29  *A FTP client.
30  *
31  *The Ftp class is a very simple FTP client that allows you to communicate with a FTP server.
32  *
33  *The FTP protocol allows you to manipulate a remote file system (list files, upload, download, create, remove, ...).
34  */
35 class Ftp
36 {
37 	///Enumeration of transfer modes.
38 	enum TransferMode
39 	{
40 		///Binary mode (file is transfered as a sequence of bytes) 
41 		Binary,
42 		///Text mode using ASCII encoding.
43 		Ascii,
44 		///Text mode using EBCDIC encoding. 
45 		Ebcdic,
46 	}
47 	
48 	package sfFtp* sfPtr;
49 	
50 	///Default Constructor.
51 	this()
52 	{
53 		sfPtr = sfFtp_create();
54 	}
55 
56 	///Destructor
57 	~this()
58 	{
59 		import dsfml.system.config;
60 		mixin(destructorOutput);
61 		sfFtp_destroy(sfPtr);
62 	}
63 
64 
65 	///Get the current working directory.
66 	///
67 	///The working directory is the root path for subsequent operations involving directories and/or filenames.
68 	///
69 	///Returns: Server response to the request.
70 	DirectoryResponse getWorkingDirectory()
71 	{
72 		return new DirectoryResponse(sfFtp_getWorkingDirectory(sfPtr));
73 	}
74 	
75 	///Get the contents of the given directory.
76 	///
77 	///This function retrieves the sub-directories and files contained in the given directory. It is not recursive. The directory parameter is relative to the current working directory.
78 	///
79 	///Returns: Server response to the request.
80 	ListingResponse getDirectoryListing(string directory = "")
81 	{
82 		import dsfml.system..string;
83 		return new ListingResponse(sfFtp_getDirectoryListing(sfPtr, toStringz(directory)));
84 	}
85 	///Change the current working directory.
86 	///
87 	///The new directory must be relative to the current one.
88 	///
89 	///Returns: Server response to the request.
90 	Response changeDirectory(string directory)
91 	{
92 		import dsfml.system..string;
93 		return new Response(sfFtp_changeDirectory(sfPtr,toStringz(directory)));
94 	}
95 
96 	///Connect to the specified FTP server.
97 	///
98 	///The port has a default value of 21, which is the standard port used by the FTP protocol. You shouldn't use a different value, unless you really know what you do.
99 	///This function tries to connect to the server so it may take a while to complete, especially if the server is not reachable. To avoid blocking your application for too long, you can use a timeout. The default value, Time::Zero, means that the system timeout will be used (which is usually pretty long).
100 	///
101 	///Params:
102 	///		address = Address of the FTP server to connect to.
103 	///		port    = Port used for the connection.
104 	///		timeout = Maximum time to wait.
105 	///
106 	///Returns: Server response to the request.
107 	Response connect(IpAddress address, ushort port = 21, Duration timeout = Duration.zero())
108 	{
109 		return new Response(sfFtp_connect(sfPtr, address.m_address.ptr, port, timeout.total!"usecs"));
110 	}
111 
112 	///Connect to the specified FTP server.
113 	///
114 	///The port has a default value of 21, which is the standard port used by the FTP protocol. You shouldn't use a different value, unless you really know what you do.
115 	///This function tries to connect to the server so it may take a while to complete, especially if the server is not reachable. To avoid blocking your application for too long, you can use a timeout. The default value, Time::Zero, means that the system timeout will be used (which is usually pretty long).
116 	///
117 	///Params:
118 	///		address = Name or ddress of the FTP server to connect to.
119 	///		port    = Port used for the connection.
120 	///		timeout = Maximum time to wait.
121 	///
122 	///Returns: Server response to the request.
123 	Response connect(string address, ushort port = 21, Duration timeout = Duration.zero())
124 	{
125 		return new Response(sfFtp_connect(sfPtr, IpAddress(address).m_address.ptr, port, timeout.total!"usecs"));
126 	}
127 
128 	///Remove an existing directory.
129 	///
130 	///The directory to remove must be relative to the current working directory. Use this function with caution, the directory will be removed permanently!
131 	///
132 	///Params:
133 	///		name = Name of the directory to remove.
134 	///
135 	///Returns: Server response to the request.
136 	Response deleteDirectory(string name)
137 	{
138 		import dsfml.system..string;
139 		return new Response(sfFtp_deleteDirectory(sfPtr, toStringz(name)));
140 	}
141 
142 	///Remove an existing file.
143 	///
144 	///The file name must be relative to the current working directory. Use this function with caution, the file will be removed permanently!
145 	///
146 	///Params:
147 	///		name = Name of the file to remove.
148 	///
149 	///Returns: Server response to the request.
150 	Response deleteFile(string name)
151 	{
152 		import dsfml.system..string;
153 		return new Response(sfFtp_deleteFile(sfPtr, toStringz(name)));
154 	}
155 
156 	///Close the connection with the server.
157 	///
158 	///Returns: Server response to the request.
159 	Response disconnect()
160 	{
161 		import dsfml.system..string;
162 		return new Response(sfFtp_disconnect(sfPtr));
163 	}
164 
165 	///Download a file from the server.
166 	///
167 	///The filename of the distant file is relative to the current working directory of the server, and the local destination path is relative to the current directory of your application.
168 	///
169 	///Params:
170 	///		remoteFile = Filename of the distant file to download.
171 	///		localPath  = Where to put to file on the local computer.
172 	///		mode = Transfer mode.
173 	///
174 	///Returns: Server response to the request.
175 	Response download(string remoteFile, string localPath, TransferMode mode = TransferMode.Binary)
176 	{
177 		import dsfml.system..string;
178 		return new Response(sfFtp_download(sfPtr, toStringz(remoteFile),toStringz(localPath),mode));
179 	}
180 
181 	///Send a null command to keep the connection alive.
182 	///
183 	///This command is useful because the server may close the connection automatically if no command is sent.
184 	///
185 	///Returns: Server response to the request.
186 	Response keepAlive()
187 	{
188 		return new Response(sfFtp_keepAlive(sfPtr));
189 	}
190 
191 	///Log in using an anonymous account.
192 	///
193 	///Logging in is mandatory after connecting to the server. Users that are not logged in cannot perform any operation.
194 	///
195 	///Returns: Server response to the request.
196 	Response login()
197 	{
198 		return new Response(sfFtp_loginAnonymous(sfPtr));
199 	}
200 	
201 	///Log in using a username and a password.
202 	///
203 	///Logging in is mandatory after connecting to the server. Users that are not logged in cannot perform any operation.
204 	///
205 	///Params:
206 	///		name = User name.
207 	///		password = The password.
208 	///
209 	///Returns: Server response to the request.
210 	Response login(string name, string password)
211 	{
212 		import dsfml.system..string;
213 		return new Response(sfFtp_login(sfPtr,toStringz(name), toStringz(password)));
214 	}
215 
216 	///Go to the parent directory of the current one. 
217 	///
218 	///Returns: Server response to the request.
219 	Response parentDirectory()
220 	{
221 		import dsfml.system..string;
222 		return new Response(sfFtp_parentDirectory(sfPtr));
223 	}
224 
225 	///Create a new directory.
226 	///
227 	///The new directory is created as a child of the current working directory.
228 	///
229 	///Params:
230 	///		name = Name of the directory to create.
231 	///
232 	///Returns: Server response to the request.
233 	Response createDirectory(string name)
234 	{
235 		import dsfml.system..string;
236 		return new Response(sfFtp_createDirectory(sfPtr, toStringz(name)));
237 	}
238 
239 	///Rename an existing file.
240 	///
241 	///The filenames must be relative to the current working directory.
242 	///
243 	///Params:
244 	///		file = File to rename.
245 	///		newName = New name of the file.
246 	///
247 	///Returns: Server response to the request.
248 	Response renameFile(string file, string newName)
249 	{
250 		import dsfml.system..string;
251 		return new Response(sfFtp_renameFile(sfPtr,toStringz(file),toStringz(newName)));
252 	}
253 
254 	///Upload a file to the server.
255 	///
256 	///The name of the local file is relative to the current working directory of your application, and the remote path is relative to the current directory of the FTP server.
257 	///
258 	///Params:
259 	///		localFile = Path of the local file to upload.
260 	///		remotePath = Where to put the file on the server.
261 	///		mode = Transfer mode.
262 	///
263 	///Returns: Server response to the request.
264 	Response upload(string localFile, string remotePath, TransferMode mode = TransferMode.Binary)
265 	{
266 		import dsfml.system..string;
267 		return new Response(sfFtp_upload(sfPtr,toStringz(localFile),toStringz(remotePath),mode));
268 	}
269 
270 	///Specialization of FTP response returning a directory.
271 	class DirectoryResponse:Response
272 	{
273 		private string Directory;
274 
275 		//Internally used constructor
276 		package this(sfFtpDirectoryResponse* FtpDirectoryResponce)
277 		{
278 			import dsfml.system..string;
279 			
280 			Directory = dsfml.system..string.toString(sfFtpDirectoryResponse_getDirectory(FtpDirectoryResponce));
281 			
282 			super(sfFtpDirectoryResponse_getStatus(FtpDirectoryResponce), sfFtpDirectoryResponse_getMessage(FtpDirectoryResponce));
283 			
284 			sfFtpDirectoryResponse_destroy(FtpDirectoryResponce);
285 		}
286 
287 		///Get the directory returned in the response.
288 		///
289 		///Returns: Directory name.
290 		string getDirectory()
291 		{
292 			return Directory;
293 		}
294 	}
295 	
296 	///Specialization of FTP response returning a filename lisiting. 
297 	class ListingResponse:Response
298 	{
299 		private string[] Filenames;
300 
301 		//Internally used constructor
302 		package this(sfFtpListingResponse* FtpListingResponce)
303 		{
304 			import dsfml.system..string;
305 
306 			Filenames.length = sfFtpListingResponse_getCount(FtpListingResponce);
307 			for(int i = 0; i < Filenames.length; i++)
308 			{
309 				Filenames[i] = dsfml.system..string.toString(sfFtpListingResponse_getName(FtpListingResponce,i));
310 			}
311 			
312 			super(sfFtpListingResponse_getStatus(FtpListingResponce), sfFtpListingResponse_getMessage(FtpListingResponce));
313 			
314 			sfFtpListingResponse_destroy(FtpListingResponce);
315 			
316 		}
317 		
318 		///Return the array of directory/file names.
319 		///
320 		///Returns: Array containing the requested listing.
321 		const(string[]) getFilenames()
322 		{
323 			return Filenames;
324 		}
325 	}
326 	
327 	///Define a FTP response. 
328 	class Response
329 	{
330 		///Status codes possibly returned by a FTP response.
331 		enum Status
332 		{
333 			RestartMarkerReply = 110,
334 			ServiceReadySoon = 120,
335 			DataConnectionAlreadyOpened = 125,
336 			OpeningDataConnection = 150,
337 			
338 			Ok = 200,
339 			PointlessCommand = 202,
340 			SystemStatus = 211,
341 			DirectoryStatus = 212,
342 			FileStatus = 213,
343 			HelpMessage = 214,
344 			SystemType = 215,
345 			ServiceReady = 220,
346 			ClosingConnection = 221,
347 			DataConnectionOpened = 225,
348 			ClosingDataConnection = 226,
349 			EnteringPassiveMode = 227,
350 			LoggedIn = 230,
351 			FileActionOk = 250,
352 			DirectoryOk = 257,
353 			
354 			NeedPassword = 331,
355 			NeedAccountToLogIn = 332,
356 			NeedInformation = 350,
357 			ServiceUnavailable = 421,
358 			DataConnectionUnavailable = 425,
359 			TransferAborted = 426,
360 			FileActionAborted = 450,
361 			LocalError = 451,
362 			InsufficientStorageSpace = 452,
363 			
364 			CommandUnknown = 500,
365 			ParametersUnknown = 501,
366 			CommandNotImplemented = 502,
367 			BadCommandSequence = 503,
368 			ParameterNotImplemented = 504,
369 			NotLoggedIn = 530,
370 			NeedAccountToStore = 532,
371 			FileUnavailable = 550,
372 			PageTypeUnknown = 551,
373 			NotEnoughMemory = 552,
374 			FilenameNotAllowed = 553,
375 			
376 			InvalidResponse = 1000,
377 			ConnectionFailed = 1001,
378 			ConnectionClosed = 1002,
379 			InvalidFile = 1003,
380 		}
381 
382 		private Status FtpStatus;
383 		private string Message;
384 
385 		//Internally used constructor.
386 		package this(sfFtpResponse* FtpResponce)
387 		{
388 			this(sfFtpResponse_getStatus(FtpResponce),sfFtpResponse_getMessage(FtpResponce));
389 			sfFtpResponse_destroy(FtpResponce);
390 		}
391 
392 		//Internally used constructor.
393 		package this(Ftp.Response.Status status = Ftp.Response.Status.InvalidResponse, const(char)* message = "")
394 		{
395 			import dsfml.system..string;
396 			FtpStatus = status;
397 			Message = dsfml.system..string.toString(message);
398 		}
399 
400 		///Get the full message contained in the response.
401 		///
402 		///Returns: The response message.
403 		string getMessage() const
404 		{
405 			return Message;
406 		}
407 		
408 		///Get the status code of the response.
409 		///
410 		///Returns: Status code. 
411 		Status getStatus() const
412 		{
413 			return FtpStatus;
414 		}
415 
416 		///Check if the status code means a success.
417 		///
418 		///This function is defined for convenience, it is equivalent to testing if the status code is < 400.
419 		///
420 		///Returns: True if the status is a success, false if it is a failure.
421 		bool isOk() const
422 		{
423 			return FtpStatus< 400;
424 		}
425 	}
426 }
427 unittest
428 {
429 	version(DSFML_Unittest_Network)
430 	{
431 		import std.stdio;
432 		import dsfml.system.err;
433 
434 		writeln("Unittest for Ftp");
435 
436 		auto ftp = new Ftp();
437 
438 		auto responce = ftp.connect("ftp.microsoft.com");
439 
440 		if(responce.isOk())
441 		{
442 			writeln("Connected! Huzzah!");
443 		}
444 		else
445 		{
446 			writeln("Uh-oh");
447 			writeln(responce.getStatus());
448 			assert(0);
449 		}
450 
451 		//annonymous log in
452 		responce = ftp.login();
453 		if(responce.isOk())
454 		{
455 			writeln("Logged in! Huzzah!");
456 		}
457 		else
458 		{
459 			writeln("Uh-oh");
460 			writeln(responce.getStatus());
461 			assert(0);
462 		}
463 
464 
465 		auto directory = ftp.getWorkingDirectory();
466 		if (directory.isOk())
467 		{
468 			writeln("Working directory: ", directory.getDirectory());
469 		}
470 
471 		auto listing = ftp.getDirectoryListing();
472 
473 		if(listing.isOk())
474 		{
475 			const(string[]) list = listing.getFilenames();
476 
477 			size_t length;
478 
479 			if(list.length > 10)
480 			{
481 				length = 10;
482 			}
483 			else
484 			{
485 				length = list.length;
486 			}
487 
488 			for(int i= 0; i < length; ++i)
489 			{
490 				writeln(list[i]);
491 			}
492 		}
493 
494 		writeln();
495 	}
496 }
497 
498 private extern(C):
499 
500 
501 struct sfFtpDirectoryResponse;
502 struct sfFtpListingResponse;
503 struct sfFtpResponse;
504 struct sfFtp;
505 
506 //FTP Listing Response Functions
507 
508 ///Destroy a FTP listing response
509 void sfFtpListingResponse_destroy(sfFtpListingResponse* ftpListingResponse);
510 
511 
512 ///Get the status code of a FTP listing response
513 Ftp.Response.Status sfFtpListingResponse_getStatus(const sfFtpListingResponse* ftpListingResponse);
514 
515 
516 ///Get the full message contained in a FTP listing response
517  const(char)* sfFtpListingResponse_getMessage(const(sfFtpListingResponse)* ftpListingResponse);
518 
519 
520 ///Return the number of directory/file names contained in a FTP listing response
521  size_t sfFtpListingResponse_getCount(const(sfFtpListingResponse)* ftpListingResponse);
522 
523 
524 ///Return a directory/file name contained in a FTP listing response
525  const(char)* sfFtpListingResponse_getName(const(sfFtpListingResponse)* ftpListingResponse, size_t index);
526 
527 
528 
529 //FTP Directory Responce Functions
530 
531 ///Destroy a FTP directory response
532  void sfFtpDirectoryResponse_destroy(sfFtpDirectoryResponse* ftpDirectoryResponse);
533 
534 
535 ///Get the status code of a FTP directory response
536 Ftp.Response.Status sfFtpDirectoryResponse_getStatus(const(sfFtpDirectoryResponse)* ftpDirectoryResponse);
537 
538 
539 ///Get the full message contained in a FTP directory response
540  const(char)* sfFtpDirectoryResponse_getMessage(const(sfFtpDirectoryResponse)* ftpDirectoryResponse);
541 
542 
543 ///Get the directory returned in a FTP directory response
544  const(char)* sfFtpDirectoryResponse_getDirectory(const(sfFtpDirectoryResponse)* ftpDirectoryResponse);
545 
546 
547 
548 //FTP Responce functions
549 
550 ///Destroy a FTP response
551  void sfFtpResponse_destroy(sfFtpResponse* ftpResponse);
552 
553 
554 ///Get the status code of a FTP response
555 Ftp.Response.Status sfFtpResponse_getStatus(const(sfFtpResponse)* ftpResponse);
556 
557 
558 ///Get the full message contained in a FTP response
559 const (char)* sfFtpResponse_getMessage(const sfFtpResponse* ftpResponse);
560 
561 
562 ////FTP functions
563 
564 ///Create a new Ftp object
565 sfFtp* sfFtp_create();
566 
567 
568 ///Destroy a Ftp object
569 void sfFtp_destroy(sfFtp* ftp);
570 
571 
572 ///Connect to the specified FTP server
573 sfFtpResponse* sfFtp_connect(sfFtp* ftp, const(char)* serverIP, ushort port, long timeout);
574 
575 
576 ///Log in using an anonymous account
577 sfFtpResponse* sfFtp_loginAnonymous(sfFtp* ftp);
578 
579 
580 ///Log in using a username and a password
581 sfFtpResponse* sfFtp_login(sfFtp* ftp, const(char)* userName, const(char)* password);
582 
583 
584 ///Close the connection with the server
585 sfFtpResponse* sfFtp_disconnect(sfFtp* ftp);
586 
587 
588 ///Send a null command to keep the connection alive
589 sfFtpResponse* sfFtp_keepAlive(sfFtp* ftp);
590 
591 
592 ///Get the current working directory
593 sfFtpDirectoryResponse* sfFtp_getWorkingDirectory(sfFtp* ftp);
594 
595 
596 ///Get the contents of the given directory
597 sfFtpListingResponse* sfFtp_getDirectoryListing(sfFtp* ftp, const(char)* directory);
598 
599 
600 ///Change the current working directory
601 sfFtpResponse* sfFtp_changeDirectory(sfFtp* ftp, const(char)* directory);
602 
603 
604 ///Go to the parent directory of the current one
605 sfFtpResponse* sfFtp_parentDirectory(sfFtp* ftp);
606 
607 
608 ///Create a new directory
609 sfFtpResponse* sfFtp_createDirectory(sfFtp* ftp, const(char)* name);
610 
611 
612 ///Remove an existing directory
613 sfFtpResponse* sfFtp_deleteDirectory(sfFtp* ftp, const(char)* name);
614 
615 
616 ///Rename an existing file
617 sfFtpResponse* sfFtp_renameFile(sfFtp* ftp, const(char)* file, const(char)* newName);
618 
619 
620 ///Remove an existing file
621 sfFtpResponse* sfFtp_deleteFile(sfFtp* ftp, const(char)* name);
622 
623 
624 ///Download a file from a FTP server
625 sfFtpResponse* sfFtp_download(sfFtp* ftp, const(char)* distantFile, const(char)* destPath, int mode);
626 
627 
628 ///Upload a file to a FTP server
629 sfFtpResponse* sfFtp_upload(sfFtp* ftp, const(char)* localFile, const(char)* destPath, int mode);