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