Ftp.cpp
1
2//
3// SFML - Simple and Fast Multimedia Library
4// Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@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,
10// including commercial applications, and to alter it and redistribute it freely,
11// subject to the following restrictions:
12//
13// 1. The origin of this software must not be misrepresented;
14// you must not claim that you wrote the original software.
15// If you use this software in a product, an acknowledgment
16// in the product documentation would be appreciated but is not required.
17//
18// 2. Altered source versions must be plainly marked as such,
19// and must not be misrepresented as being the original software.
20//
21// 3. This notice may not be removed or altered from any source distribution.
22//
24
26// Headers
28#include <SFML/Network/Ftp.hpp>
29#include <SFML/Network/IPAddress.hpp>
30#include <algorithm>
31#include <fstream>
32#include <iterator>
33#include <sstream>
34
35
36namespace sf
37{
39// Utility class for exchanging stuff with the server
40// on the data channel
43{
44public :
45
47 // Constructor
49 DataChannel(Ftp& Owner);
50
52 // Destructor
55
57 // Open the data channel using the specified mode and port
60
62 // Send data on the data channel
64 void Send(const std::vector<char>& Data);
65
67 // Receive data on the data channel until it is closed
69 void Receive(std::vector<char>& Data);
70
71private :
72
74 // Member data
76 Ftp& myFtp;
77 SocketTCP myDataSocket;
78};
79
80
84Ftp::Response::Response(Status Code, const std::string& Message) :
85myStatus (Code),
86myMessage(Message)
87{
88
89}
90
91
97{
98 return myStatus < 400;
99}
100
101
106{
107 return myStatus;
108}
109
110
114const std::string& Ftp::Response::GetMessage() const
115{
116 return myMessage;
117}
118
119
124Ftp::Response(Resp)
125{
126 if (IsOk())
127 {
128 // Extract the directory from the server response
129 std::string::size_type Begin = Resp.GetMessage().find('"', 0);
130 std::string::size_type End = Resp.GetMessage().find('"', Begin + 1);
131 myDirectory = Resp.GetMessage().substr(Begin + 1, End - Begin - 1);
132 }
133}
134
135
139const std::string& Ftp::DirectoryResponse::GetDirectory() const
140{
141 return myDirectory;
142}
143
144
148Ftp::ListingResponse::ListingResponse(Ftp::Response Resp, const std::vector<char>& Data) :
149Ftp::Response(Resp)
150{
151 if (IsOk())
152 {
153 // Fill the array of strings
154 std::string Paths(Data.begin(), Data.end());
155 std::string::size_type LastPos = 0;
156 for (std::string::size_type Pos = Paths.find("\r\n"); Pos != std::string::npos; Pos = Paths.find("\r\n", LastPos))
157 {
158 myFilenames.push_back(Paths.substr(LastPos, Pos - LastPos));
159 LastPos = Pos + 2;
160 }
161 }
162}
163
164
169{
170 return myFilenames.size();
171}
172
173
177const std::string& Ftp::ListingResponse::GetFilename(std::size_t Index) const
178{
179 return myFilenames[Index];
180}
181
182
187{
188 Disconnect();
189}
190
191
195Ftp::Response Ftp::Connect(const IPAddress& Server, unsigned short Port, float Timeout)
196{
197 // Connect to the server
198 if (myCommandSocket.Connect(Port, Server, Timeout) != Socket::Done)
200
201 // Get the response to the connection
202 return GetResponse();
203}
204
205
210{
211 return Login("anonymous", "user@sfml-dev.org");
212}
213
214
218Ftp::Response Ftp::Login(const std::string& UserName, const std::string& Password)
219{
220 Response Resp = SendCommand("USER", UserName);
221 if (Resp.IsOk())
222 Resp = SendCommand("PASS", Password);
223
224 return Resp;
225}
226
227
232{
233 // Send the exit command
234 Response Resp = SendCommand("QUIT");
235 if (Resp.IsOk())
236 myCommandSocket.Close();
237
238 return Resp;
239}
240
241
246{
247 return SendCommand("NOOP");
248}
249
250
255{
256 return DirectoryResponse(SendCommand("PWD"));
257}
258
259
265{
266 // Open a data channel on default port (20) using ASCII transfer mode
267 std::vector<char> DirData;
268 DataChannel Data(*this);
269 Response Resp = Data.Open(Ascii);
270 if (Resp.IsOk())
271 {
272 // Tell the server to send us the listing
273 Resp = SendCommand("NLST", Directory);
274 if (Resp.IsOk())
275 {
276 // Receive the listing
277 Data.Receive(DirData);
278
279 // Get the response from the server
280 Resp = GetResponse();
281 }
282 }
283
284 return ListingResponse(Resp, DirData);
285}
286
287
291Ftp::Response Ftp::ChangeDirectory(const std::string& Directory)
292{
293 return SendCommand("CWD", Directory);
294}
295
296
301{
302 return SendCommand("CDUP");
303}
304
305
309Ftp::Response Ftp::MakeDirectory(const std::string& Name)
310{
311 return SendCommand("MKD", Name);
312}
313
314
318Ftp::Response Ftp::DeleteDirectory(const std::string& Name)
319{
320 return SendCommand("RMD", Name);
321}
322
323
327Ftp::Response Ftp::RenameFile(const std::string& File, const std::string& NewName)
328{
329 Response Resp = SendCommand("RNFR", File);
330 if (Resp.IsOk())
331 Resp = SendCommand("RNTO", NewName);
332
333 return Resp;
334}
335
336
340Ftp::Response Ftp::DeleteFile(const std::string& Name)
341{
342 return SendCommand("DELE", Name);
343}
344
345
349Ftp::Response Ftp::Download(const std::string& DistantFile, const std::string& DestPath, TransferMode Mode)
350{
351 // Open a data channel using the given transfer mode
352 DataChannel Data(*this);
353 Response Resp = Data.Open(Mode);
354 if (Resp.IsOk())
355 {
356 // Tell the server to start the transfer
357 Resp = SendCommand("RETR", DistantFile);
358 if (Resp.IsOk())
359 {
360 // Receive the file data
361 std::vector<char> FileData;
362 Data.Receive(FileData);
363
364 // Get the response from the server
365 Resp = GetResponse();
366 if (Resp.IsOk())
367 {
368 // Extract the filename from the file path
369 std::string Filename = DistantFile;
370 std::string::size_type Pos = Filename.find_last_of("/\\");
371 if (Pos != std::string::npos)
372 Filename = Filename.substr(Pos + 1);
373
374 // Make sure the destination path ends with a slash
375 std::string Path = DestPath;
376 if (!Path.empty() && (Path[Path.size() - 1] != '\\') && (Path[Path.size() - 1] != '/'))
377 Path += "/";
378
379 // Create the file and copy the received data into it
380 std::ofstream File((Path + Filename).c_str(), std::ios_base::binary);
381 if (!File)
383 if (!FileData.empty())
384 File.write(&FileData[0], static_cast<std::streamsize>(FileData.size()));
385 }
386 }
387 }
388
389 return Resp;
390}
391
392
396Ftp::Response Ftp::Upload(const std::string& LocalFile, const std::string& DestPath, TransferMode Mode)
397{
398 // Get the contents of the file to send
399 std::ifstream File(LocalFile.c_str(), std::ios_base::binary);
400 if (!File)
402 File.seekg(0, std::ios::end);
403 std::size_t Length = File.tellg();
404 File.seekg(0, std::ios::beg);
405 std::vector<char> FileData(Length);
406 if (Length > 0)
407 File.read(&FileData[0], static_cast<std::streamsize>(Length));
408
409 // Extract the filename from the file path
410 std::string Filename = LocalFile;
411 std::string::size_type Pos = Filename.find_last_of("/\\");
412 if (Pos != std::string::npos)
413 Filename = Filename.substr(Pos + 1);
414
415 // Make sure the destination path ends with a slash
416 std::string Path = DestPath;
417 if (!Path.empty() && (Path[Path.size() - 1] != '\\') && (Path[Path.size() - 1] != '/'))
418 Path += "/";
419
420 // Open a data channel using the given transfer mode
421 DataChannel Data(*this);
422 Response Resp = Data.Open(Mode);
423 if (Resp.IsOk())
424 {
425 // Tell the server to start the transfer
426 Resp = SendCommand("STOR", Path + Filename);
427 if (Resp.IsOk())
428 {
429 // Send the file data
430 Data.Send(FileData);
431
432 // Get the response from the server
433 Resp = GetResponse();
434 }
435 }
436
437 return Resp;
438}
439
440
444Ftp::Response Ftp::SendCommand(const std::string& Command, const std::string& Parameter)
445{
446 // Build the command string
447 std::string CommandStr;
448 if (Parameter != "")
449 CommandStr = Command + " " + Parameter + "\r\n";
450 else
451 CommandStr = Command + "\r\n";
452
453 // Send it to the server
454 if (myCommandSocket.Send(CommandStr.c_str(), CommandStr.length()) != sf::Socket::Done)
455 return Response(Response::ConnectionClosed);
456
457 // Get the response
458 return GetResponse();
459}
460
461
466Ftp::Response Ftp::GetResponse()
467{
468 // We'll use a variable to keep track of the last valid code.
469 // It is useful in case of multi-lines responses, because the end of such a response
470 // will start by the same code
471 unsigned int LastCode = 0;
472 bool IsInsideMultiline = false;
473 std::string Message;
474
475 for (;;)
476 {
477 // Receive the response from the server
478 char Buffer[1024];
479 std::size_t Length;
480 if (myCommandSocket.Receive(Buffer, sizeof(Buffer), Length) != sf::Socket::Done)
481 return Response(Response::ConnectionClosed);
482
483 // There can be several lines inside the received buffer, extract them all
484 std::istringstream In(std::string(Buffer, Length), std::ios_base::binary);
485 while (In)
486 {
487 // Try to extract the code
488 unsigned int Code;
489 if (In >> Code)
490 {
491 // Extract the separator
492 char Sep;
493 In.get(Sep);
494
495 // The '-' character means a multiline response
496 if ((Sep == '-') && !IsInsideMultiline)
497 {
498 // Set the multiline flag
499 IsInsideMultiline = true;
500
501 // Keep track of the code
502 if (LastCode == 0)
503 LastCode = Code;
504
505 // Extract the line
506 std::getline(In, Message);
507
508 // Remove the ending '\r' (all lines are terminated by "\r\n")
509 Message.erase(Message.length() - 1);
510 Message = Sep + Message + "\n";
511 }
512 else
513 {
514 // We must make sure that the code is the same, otherwise it means
515 // we haven't reached the end of the multiline response
516 if ((Sep != '-') && ((Code == LastCode) || (LastCode == 0)))
517 {
518 // Clear the multiline flag
519 IsInsideMultiline = false;
520
521 // Extract the line
522 std::string Line;
523 std::getline(In, Line);
524
525 // Remove the ending '\r' (all lines are terminated by "\r\n")
526 Line.erase(Line.length() - 1);
527
528 // Append it to the message
529 if (Code == LastCode)
530 {
531 std::ostringstream Out;
532 Out << Code << Sep << Line;
533 Message += Out.str();
534 }
535 else
536 {
537 Message = Sep + Line;
538 }
539
540 // Return the response code and message
541 return Response(static_cast<Response::Status>(Code), Message);
542 }
543 else
544 {
545 // The line we just read was actually not a response,
546 // only a new part of the current multiline response
547
548 // Extract the line
549 std::string Line;
550 std::getline(In, Line);
551
552 if (!Line.empty())
553 {
554 // Remove the ending '\r' (all lines are terminated by "\r\n")
555 Line.erase(Line.length() - 1);
556
557 // Append it to the current message
558 std::ostringstream Out;
559 Out << Code << Sep << Line << "\n";
560 Message += Out.str();
561 }
562 }
563 }
564 }
565 else if (LastCode != 0)
566 {
567 // It seems we are in the middle of a multiline response
568
569 // Clear the error bits of the stream
570 In.clear();
571
572 // Extract the line
573 std::string Line;
574 std::getline(In, Line);
575
576 if (!Line.empty())
577 {
578 // Remove the ending '\r' (all lines are terminated by "\r\n")
579 Line.erase(Line.length() - 1);
580
581 // Append it to the current message
582 Message += Line + "\n";
583 }
584 }
585 else
586 {
587 // Error : cannot extract the code, and we are not in a multiline response
589 }
590 }
591 }
592
593 // We never reach there
594}
595
596
601myFtp(Owner)
602{
603
604}
605
606
611{
612 // Close the data socket
613 myDataSocket.Close();
614}
615
616
621{
622 // Open a data connection in active mode (we connect to the server)
623 Ftp::Response Resp = myFtp.SendCommand("PASV");
624 if (Resp.IsOk())
625 {
626 // Extract the connection address and port from the response
627 std::string::size_type begin = Resp.GetMessage().find_first_of("0123456789");
628 if (begin != std::string::npos)
629 {
630 sf::Uint8 Data[6] = {0, 0, 0, 0, 0, 0};
631 std::string Str = Resp.GetMessage().substr(begin);
632 std::size_t Index = 0;
633 for (int i = 0; i < 6; ++i)
634 {
635 // Extract the current number
636 while (isdigit(Str[Index]))
637 {
638 Data[i] = Data[i] * 10 + (Str[Index] - '0');
639 Index++;
640 }
641
642 // Skip separator
643 Index++;
644 }
645
646 // Reconstruct connection port and address
647 unsigned short Port = Data[4] * 256 + Data[5];
648 sf::IPAddress Address(static_cast<sf::Uint8>(Data[0]),
649 static_cast<sf::Uint8>(Data[1]),
650 static_cast<sf::Uint8>(Data[2]),
651 static_cast<sf::Uint8>(Data[3]));
652
653 // Connect the data channel to the server
654 if (myDataSocket.Connect(Port, Address) == Socket::Done)
655 {
656 // Translate the transfer mode to the corresponding FTP parameter
657 std::string ModeStr;
658 switch (Mode)
659 {
660 case Ftp::Binary : ModeStr = "I"; break;
661 case Ftp::Ascii : ModeStr = "A"; break;
662 case Ftp::Ebcdic : ModeStr = "E"; break;
663 }
664
665 // Set the transfer mode
666 Resp = myFtp.SendCommand("TYPE", ModeStr);
667 }
668 else
669 {
670 // Failed to connect to the server
672 }
673 }
674 }
675
676 return Resp;
677}
678
679
683void Ftp::DataChannel::Receive(std::vector<char>& Data)
684{
685 // Receive data
686 Data.clear();
687 char Buffer[1024];
688 std::size_t Received;
689 while (myDataSocket.Receive(Buffer, sizeof(Buffer), Received) == sf::Socket::Done)
690 {
691 std::copy(Buffer, Buffer + Received, std::back_inserter(Data));
692 }
693
694 // Close the data socket
695 myDataSocket.Close();
696}
697
698
702void Ftp::DataChannel::Send(const std::vector<char>& Data)
703{
704 // Send data
705 if (!Data.empty())
706 myDataSocket.Send(&Data[0], Data.size());
707
708 // Close the data socket
709 myDataSocket.Close();
710}
711
712} // namespace sf
Ftp::Response Open(Ftp::TransferMode Mode)
Open the data channel using the specified mode and port.
Definition Ftp.cpp:620
~DataChannel()
Destructor.
Definition Ftp.cpp:610
void Send(const std::vector< char > &Data)
Send data on the data channel.
Definition Ftp.cpp:702
void Receive(std::vector< char > &Data)
Receive data on the data channel until it is closed.
Definition Ftp.cpp:683
DataChannel(Ftp &Owner)
Constructor.
Definition Ftp.cpp:600
Specialization of FTP response returning a directory.
Definition Ftp.hpp:183
DirectoryResponse(Response Resp)
Default constructor.
Definition Ftp.cpp:123
const std::string & GetDirectory() const
Get the directory returned in the response.
Definition Ftp.cpp:139
Specialization of FTP response returning a filename lisiting.
Definition Ftp.hpp:215
std::size_t GetCount() const
Get the number of filenames in the listing.
Definition Ftp.cpp:168
const std::string & GetFilename(std::size_t Index) const
Get the Index-th filename in the directory.
Definition Ftp.cpp:177
ListingResponse(Response Resp, const std::vector< char > &Data)
Default constructor.
Definition Ftp.cpp:148
This class wraps a FTP response, which is basically :
Definition Ftp.hpp:67
Response(Status Code=InvalidResponse, const std::string &Message="")
Default constructor.
Definition Ftp.cpp:84
const std::string & GetMessage() const
Get the full message contained in the response.
Definition Ftp.cpp:114
Status GetStatus() const
Get the response status code.
Definition Ftp.cpp:105
bool IsOk() const
Convenience function to check if the response status code means a success.
Definition Ftp.cpp:96
Status
Enumerate all the valid status codes returned in a FTP response.
Definition Ftp.hpp:75
@ ConnectionFailed
Connection with server failed.
Definition Ftp.hpp:131
@ InvalidResponse
Response is not a valid FTP one.
Definition Ftp.hpp:130
@ ConnectionClosed
Connection with server closed.
Definition Ftp.hpp:132
@ InvalidFile
Invalid file to upload / download.
Definition Ftp.hpp:133
This class provides methods for manipulating the FTP protocol (described in RFC 959).
Definition Ftp.hpp:48
Response KeepAlive()
Send a null command just to prevent from being disconnected.
Definition Ftp.cpp:245
Response Download(const std::string &DistantFile, const std::string &DestPath, TransferMode Mode=Binary)
Download a file from the server.
Definition Ftp.cpp:349
TransferMode
Enumeration of transfer modes.
Definition Ftp.hpp:55
@ Binary
Binary mode (file is transfered as a sequence of bytes).
Definition Ftp.hpp:56
@ Ebcdic
Text mode using EBCDIC encoding.
Definition Ftp.hpp:58
@ Ascii
Text mode using ASCII encoding.
Definition Ftp.hpp:57
ListingResponse GetDirectoryListing(const std::string &Directory="")
Get the contents of the given directory (subdirectories and files).
Definition Ftp.cpp:264
~Ftp()
Destructor – close the connection with the server.
Definition Ftp.cpp:186
Response MakeDirectory(const std::string &Name)
Create a new directory.
Definition Ftp.cpp:309
Response Upload(const std::string &LocalFile, const std::string &DestPath, TransferMode Mode=Binary)
Upload a file to the server.
Definition Ftp.cpp:396
Response ParentDirectory()
Go to the parent directory of the current one.
Definition Ftp.cpp:300
Response Disconnect()
Close the connection with FTP server.
Definition Ftp.cpp:231
Response ChangeDirectory(const std::string &Directory)
Change the current working directory.
Definition Ftp.cpp:291
Response RenameFile(const std::string &File, const std::string &NewName)
Rename a file.
Definition Ftp.cpp:327
DirectoryResponse GetWorkingDirectory()
Get the current working directory.
Definition Ftp.cpp:254
Response DeleteDirectory(const std::string &Name)
Remove an existing directory.
Definition Ftp.cpp:318
Response Login()
Log in using anonymous account.
Definition Ftp.cpp:209
Response DeleteFile(const std::string &Name)
Remove an existing file.
Definition Ftp.cpp:340
Response Connect(const IPAddress &Server, unsigned short Port=21, float Timeout=0.f)
Connect to the specified FTP server.
Definition Ftp.cpp:195
IPAddress provides easy manipulation of IP v4 addresses.
Definition IPAddress.hpp:43
SocketTCP wraps a socket using TCP protocol to send data safely (but a bit slower).
Definition SocketTCP.hpp:46
Socket::Status Receive(char *Data, std::size_t MaxSize, std::size_t &SizeReceived)
Receive an array of bytes from the host (must be connected first).
Socket::Status Send(const char *Data, std::size_t Size)
Send an array of bytes to the host (must be connected first).
NonCopyable()
The default constructor won't be generated, so provide it.