Ftp.cpp
1 //
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 
36 namespace sf
37 {
39 // Utility class for exchanging stuff with the server
40 // on the data channel
43 {
44 public :
45 
47  // Constructor
49  DataChannel(Ftp& Owner);
50 
52  // Destructor
54  ~DataChannel();
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 
71 private :
72 
74  // Member data
76  Ftp& myFtp;
77  SocketTCP myDataSocket;
78 };
79 
80 
84 Ftp::Response::Response(Status Code, const std::string& Message) :
85 myStatus (Code),
86 myMessage(Message)
87 {
88 
89 }
90 
91 
96 bool Ftp::Response::IsOk() const
97 {
98  return myStatus < 400;
99 }
100 
101 
106 {
107  return myStatus;
108 }
109 
110 
114 const std::string& Ftp::Response::GetMessage() const
115 {
116  return myMessage;
117 }
118 
119 
124 Ftp::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 
139 const std::string& Ftp::DirectoryResponse::GetDirectory() const
140 {
141  return myDirectory;
142 }
143 
144 
148 Ftp::ListingResponse::ListingResponse(Ftp::Response Resp, const std::vector<char>& Data) :
149 Ftp::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 
177 const std::string& Ftp::ListingResponse::GetFilename(std::size_t Index) const
178 {
179  return myFilenames[Index];
180 }
181 
182 
187 {
188  Disconnect();
189 }
190 
191 
195 Ftp::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 
218 Ftp::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 
264 Ftp::ListingResponse Ftp::GetDirectoryListing(const std::string& Directory)
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 
291 Ftp::Response Ftp::ChangeDirectory(const std::string& Directory)
292 {
293  return SendCommand("CWD", Directory);
294 }
295 
296 
301 {
302  return SendCommand("CDUP");
303 }
304 
305 
309 Ftp::Response Ftp::MakeDirectory(const std::string& Name)
310 {
311  return SendCommand("MKD", Name);
312 }
313 
314 
318 Ftp::Response Ftp::DeleteDirectory(const std::string& Name)
319 {
320  return SendCommand("RMD", Name);
321 }
322 
323 
327 Ftp::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 
340 Ftp::Response Ftp::DeleteFile(const std::string& Name)
341 {
342  return SendCommand("DELE", Name);
343 }
344 
345 
349 Ftp::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 
396 Ftp::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 
444 Ftp::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 
466 Ftp::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
588  return Response(Response::InvalidResponse);
589  }
590  }
591  }
592 
593  // We never reach there
594 }
595 
596 
601 myFtp(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 
683 void 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 
702 void 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
Status
Enumerate all the valid status codes returned in a FTP response.
Definition: Ftp.hpp:74
DirectoryResponse GetWorkingDirectory()
Get the current working directory.
Definition: Ftp.cpp:254
Response DeleteFile(const std::string &Name)
Remove an existing file.
Definition: Ftp.cpp:340
Ftp::Response Open(Ftp::TransferMode Mode)
Open the data channel using the specified mode and port.
Definition: Ftp.cpp:620
Text mode using EBCDIC encoding.
Definition: Ftp.hpp:58
Connection with server failed.
Definition: Ftp.hpp:131
Status GetStatus() const
Get the response status code.
Definition: Ftp.cpp:105
void Receive(std::vector< char > &Data)
Receive data on the data channel until it is closed.
Definition: Ftp.cpp:683
Response Login()
Log in using anonymous account.
Definition: Ftp.cpp:209
Utility base class to easily declare non-copyable classes.
Definition: NonCopyable.hpp:40
Response Disconnect()
Close the connection with FTP server.
Definition: Ftp.cpp:231
Response Download(const std::string &DistantFile, const std::string &DestPath, TransferMode Mode=Binary)
Download a file from the server.
Definition: Ftp.cpp:349
Response Connect(const IPAddress &Server, unsigned short Port=21, float Timeout=0.f)
Connect to the specified FTP server.
Definition: Ftp.cpp:195
std::size_t GetCount() const
Get the number of filenames in the listing.
Definition: Ftp.cpp:168
Invalid file to upload / download.
Definition: Ftp.hpp:133
This class wraps a FTP response, which is basically :
Definition: Ftp.hpp:66
Specialization of FTP response returning a directory.
Definition: Ftp.hpp:182
bool IsOk() const
Convenience function to check if the response status code means a success.
Definition: Ftp.cpp:96
~Ftp()
Destructor – close the connection with the server.
Definition: Ftp.cpp:186
Binary mode (file is transfered as a sequence of bytes)
Definition: Ftp.hpp:56
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).
Definition: SocketTCP.cpp:272
Socket::Status Send(const char *Data, std::size_t Size)
Send an array of bytes to the host (must be connected first)
Definition: SocketTCP.cpp:235
Response KeepAlive()
Send a null command just to prevent from being disconnected.
Definition: Ftp.cpp:245
Socket::Status Connect(unsigned short Port, const IPAddress &HostAddress, float Timeout=0.f)
Connect to another computer on a specified port.
Definition: SocketTCP.cpp:70
TransferMode
Enumeration of transfer modes.
Definition: Ftp.hpp:54
Response RenameFile(const std::string &File, const std::string &NewName)
Rename a file.
Definition: Ftp.cpp:327
const std::string & GetFilename(std::size_t Index) const
Get the Index-th filename in the directory.
Definition: Ftp.cpp:177
Response ParentDirectory()
Go to the parent directory of the current one.
Definition: Ftp.cpp:300
Response MakeDirectory(const std::string &Name)
Create a new directory.
Definition: Ftp.cpp:309
Connection with server closed.
Definition: Ftp.hpp:132
ListingResponse(Response Resp, const std::vector< char > &Data)
Default constructor.
Definition: Ftp.cpp:148
ListingResponse GetDirectoryListing(const std::string &Directory="")
Get the contents of the given directory (subdirectories and files)
Definition: Ftp.cpp:264
DirectoryResponse(Response Resp)
Default constructor.
Definition: Ftp.cpp:123
Response(Status Code=InvalidResponse, const std::string &Message="")
Default constructor.
Definition: Ftp.cpp:84
Response ChangeDirectory(const std::string &Directory)
Change the current working directory.
Definition: Ftp.cpp:291
IPAddress provides easy manipulation of IP v4 addresses.
Definition: IPAddress.hpp:42
Specialization of FTP response returning a filename lisiting.
Definition: Ftp.hpp:214
Response is not a valid FTP one.
Definition: Ftp.hpp:130
This class provides methods for manipulating the FTP protocol (described in RFC 959).
Definition: Ftp.hpp:47
const std::string & GetDirectory() const
Get the directory returned in the response.
Definition: Ftp.cpp:139
DataChannel(Ftp &Owner)
Constructor.
Definition: Ftp.cpp:600
SocketTCP wraps a socket using TCP protocol to send data safely (but a bit slower) ...
Definition: SocketTCP.hpp:45
Text mode using ASCII encoding.
Definition: Ftp.hpp:57
const std::string & GetMessage() const
Get the full message contained in the response.
Definition: Ftp.cpp:114
Response Upload(const std::string &LocalFile, const std::string &DestPath, TransferMode Mode=Binary)
Upload a file to the server.
Definition: Ftp.cpp:396
bool Close()
Close the socket.
Definition: SocketTCP.cpp:406
Response DeleteDirectory(const std::string &Name)
Remove an existing directory.
Definition: Ftp.cpp:318
~DataChannel()
Destructor.
Definition: Ftp.cpp:610
void Send(const std::vector< char > &Data)
Send data on the data channel.
Definition: Ftp.cpp:702