Back to index

libsfml  1.6+dfsg2
Ftp.cpp
Go to the documentation of this file.
00001 
00002 //
00003 // SFML - Simple and Fast Multimedia Library
00004 // Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com)
00005 //
00006 // This software is provided 'as-is', without any express or implied warranty.
00007 // In no event will the authors be held liable for any damages arising from the use of this software.
00008 //
00009 // Permission is granted to anyone to use this software for any purpose,
00010 // including commercial applications, and to alter it and redistribute it freely,
00011 // subject to the following restrictions:
00012 //
00013 // 1. The origin of this software must not be misrepresented;
00014 //    you must not claim that you wrote the original software.
00015 //    If you use this software in a product, an acknowledgment
00016 //    in the product documentation would be appreciated but is not required.
00017 //
00018 // 2. Altered source versions must be plainly marked as such,
00019 //    and must not be misrepresented as being the original software.
00020 //
00021 // 3. This notice may not be removed or altered from any source distribution.
00022 //
00024 
00026 // Headers
00028 #include <SFML/Network/Ftp.hpp>
00029 #include <SFML/Network/IPAddress.hpp>
00030 #include <algorithm>
00031 #include <fstream>
00032 #include <iterator>
00033 #include <sstream>
00034 
00035 
00036 namespace sf
00037 {
00039 // Utility class for exchanging stuff with the server
00040 // on the data channel
00042 class Ftp::DataChannel : NonCopyable
00043 {
00044 public :
00045 
00047     // Constructor
00049     DataChannel(Ftp& Owner);
00050 
00052     // Destructor
00054     ~DataChannel();
00055 
00057     // Open the data channel using the specified mode and port
00059     Ftp::Response Open(Ftp::TransferMode Mode);
00060 
00062     // Send data on the data channel
00064     void Send(const std::vector<char>& Data);
00065 
00067     // Receive data on the data channel until it is closed
00069     void Receive(std::vector<char>& Data);
00070 
00071 private :
00072 
00074     // Member data
00076     Ftp&      myFtp;        
00077     SocketTCP myDataSocket; 
00078 };
00079 
00080 
00084 Ftp::Response::Response(Status Code, const std::string& Message) :
00085 myStatus (Code),
00086 myMessage(Message)
00087 {
00088 
00089 }
00090 
00091 
00096 bool Ftp::Response::IsOk() const
00097 {
00098     return myStatus < 400;
00099 }
00100 
00101 
00105 Ftp::Response::Status Ftp::Response::GetStatus() const
00106 {
00107     return myStatus;
00108 }
00109 
00110 
00114 const std::string& Ftp::Response::GetMessage() const
00115 {
00116     return myMessage;
00117 }
00118 
00119 
00123 Ftp::DirectoryResponse::DirectoryResponse(Ftp::Response Resp) :
00124 Ftp::Response(Resp)
00125 {
00126     if (IsOk())
00127     {
00128         // Extract the directory from the server response
00129         std::string::size_type Begin = Resp.GetMessage().find('"', 0);
00130         std::string::size_type End   = Resp.GetMessage().find('"', Begin + 1);
00131         myDirectory = Resp.GetMessage().substr(Begin + 1, End - Begin - 1);
00132     }
00133 }
00134 
00135 
00139 const std::string& Ftp::DirectoryResponse::GetDirectory() const
00140 {
00141     return myDirectory;
00142 }
00143 
00144 
00148 Ftp::ListingResponse::ListingResponse(Ftp::Response Resp, const std::vector<char>& Data) :
00149 Ftp::Response(Resp)
00150 {
00151     if (IsOk())
00152     {
00153         // Fill the array of strings
00154         std::string Paths(Data.begin(), Data.end());
00155         std::string::size_type LastPos = 0;
00156         for (std::string::size_type Pos = Paths.find("\r\n"); Pos != std::string::npos; Pos = Paths.find("\r\n", LastPos))
00157         {
00158             myFilenames.push_back(Paths.substr(LastPos, Pos - LastPos));
00159             LastPos = Pos + 2;
00160         }
00161     }
00162 }
00163 
00164 
00168 std::size_t Ftp::ListingResponse::GetCount() const
00169 {
00170     return myFilenames.size();
00171 }
00172 
00173 
00177 const std::string& Ftp::ListingResponse::GetFilename(std::size_t Index) const
00178 {
00179     return myFilenames[Index];
00180 }
00181 
00182 
00186 Ftp::~Ftp()
00187 {
00188     Disconnect();
00189 }
00190 
00191 
00195 Ftp::Response Ftp::Connect(const IPAddress& Server, unsigned short Port, float Timeout)
00196 {
00197     // Connect to the server
00198     if (myCommandSocket.Connect(Port, Server, Timeout) != Socket::Done)
00199         return Response(Response::ConnectionFailed);
00200 
00201     // Get the response to the connection
00202     return GetResponse();
00203 }
00204 
00205 
00209 Ftp::Response Ftp::Login()
00210 {
00211     return Login("anonymous", "user@sfml-dev.org");
00212 }
00213 
00214 
00218 Ftp::Response Ftp::Login(const std::string& UserName, const std::string& Password)
00219 {
00220     Response Resp = SendCommand("USER", UserName);
00221     if (Resp.IsOk())
00222         Resp = SendCommand("PASS", Password);
00223 
00224     return Resp;
00225 }
00226 
00227 
00231 Ftp::Response Ftp::Disconnect()
00232 {
00233     // Send the exit command
00234     Response Resp = SendCommand("QUIT");
00235     if (Resp.IsOk())
00236         myCommandSocket.Close();
00237 
00238     return Resp;
00239 }
00240 
00241 
00245 Ftp::Response Ftp::KeepAlive()
00246 {
00247     return SendCommand("NOOP");
00248 }
00249 
00250 
00254 Ftp::DirectoryResponse Ftp::GetWorkingDirectory()
00255 {
00256     return DirectoryResponse(SendCommand("PWD"));
00257 }
00258 
00259 
00264 Ftp::ListingResponse Ftp::GetDirectoryListing(const std::string& Directory)
00265 {
00266     // Open a data channel on default port (20) using ASCII transfer mode
00267     std::vector<char> DirData;
00268     DataChannel Data(*this);
00269     Response Resp = Data.Open(Ascii);
00270     if (Resp.IsOk())
00271     {
00272         // Tell the server to send us the listing
00273         Resp = SendCommand("NLST", Directory);
00274         if (Resp.IsOk())
00275         {
00276             // Receive the listing
00277             Data.Receive(DirData);
00278 
00279             // Get the response from the server
00280             Resp = GetResponse();
00281         }
00282     }
00283 
00284     return ListingResponse(Resp, DirData);
00285 }
00286 
00287 
00291 Ftp::Response Ftp::ChangeDirectory(const std::string& Directory)
00292 {
00293     return SendCommand("CWD", Directory);
00294 }
00295 
00296 
00300 Ftp::Response Ftp::ParentDirectory()
00301 {
00302     return SendCommand("CDUP");
00303 }
00304 
00305 
00309 Ftp::Response Ftp::MakeDirectory(const std::string& Name)
00310 {
00311     return SendCommand("MKD", Name);
00312 }
00313 
00314 
00318 Ftp::Response Ftp::DeleteDirectory(const std::string& Name)
00319 {
00320     return SendCommand("RMD", Name);
00321 }
00322 
00323 
00327 Ftp::Response Ftp::RenameFile(const std::string& File, const std::string& NewName)
00328 {
00329     Response Resp = SendCommand("RNFR", File);
00330     if (Resp.IsOk())
00331        Resp = SendCommand("RNTO", NewName);
00332 
00333     return Resp;
00334 }
00335 
00336 
00340 Ftp::Response Ftp::DeleteFile(const std::string& Name)
00341 {
00342     return SendCommand("DELE", Name);
00343 }
00344 
00345 
00349 Ftp::Response Ftp::Download(const std::string& DistantFile, const std::string& DestPath, TransferMode Mode)
00350 {
00351     // Open a data channel using the given transfer mode
00352     DataChannel Data(*this);
00353     Response Resp = Data.Open(Mode);
00354     if (Resp.IsOk())
00355     {
00356         // Tell the server to start the transfer
00357         Resp = SendCommand("RETR", DistantFile);
00358         if (Resp.IsOk())
00359         {
00360             // Receive the file data
00361             std::vector<char> FileData;
00362             Data.Receive(FileData);
00363 
00364             // Get the response from the server
00365             Resp = GetResponse();
00366             if (Resp.IsOk())
00367             {
00368                 // Extract the filename from the file path
00369                 std::string Filename = DistantFile;
00370                 std::string::size_type Pos = Filename.find_last_of("/\\");
00371                 if (Pos != std::string::npos)
00372                     Filename = Filename.substr(Pos + 1);
00373 
00374                 // Make sure the destination path ends with a slash
00375                 std::string Path = DestPath;
00376                 if (!Path.empty() && (Path[Path.size() - 1] != '\\') && (Path[Path.size() - 1] != '/'))
00377                     Path += "/";
00378 
00379                 // Create the file and copy the received data into it
00380                 std::ofstream File((Path + Filename).c_str(), std::ios_base::binary);
00381                 if (!File)
00382                     return Response(Response::InvalidFile);
00383                 if (!FileData.empty())
00384                     File.write(&FileData[0], static_cast<std::streamsize>(FileData.size()));
00385             }
00386         }
00387     }
00388 
00389     return Resp;
00390 }
00391 
00392 
00396 Ftp::Response Ftp::Upload(const std::string& LocalFile, const std::string& DestPath, TransferMode Mode)
00397 {
00398     // Get the contents of the file to send
00399     std::ifstream File(LocalFile.c_str(), std::ios_base::binary);
00400     if (!File)
00401         return Response(Response::InvalidFile);
00402     File.seekg(0, std::ios::end);
00403     std::size_t Length = File.tellg();
00404     File.seekg(0, std::ios::beg);
00405     std::vector<char> FileData(Length);
00406     if (Length > 0)
00407         File.read(&FileData[0], static_cast<std::streamsize>(Length));
00408 
00409     // Extract the filename from the file path
00410     std::string Filename = LocalFile;
00411     std::string::size_type Pos = Filename.find_last_of("/\\");
00412     if (Pos != std::string::npos)
00413         Filename = Filename.substr(Pos + 1);
00414 
00415     // Make sure the destination path ends with a slash
00416     std::string Path = DestPath;
00417     if (!Path.empty() && (Path[Path.size() - 1] != '\\') && (Path[Path.size() - 1] != '/'))
00418         Path += "/";
00419 
00420     // Open a data channel using the given transfer mode
00421     DataChannel Data(*this);
00422     Response Resp = Data.Open(Mode);
00423     if (Resp.IsOk())
00424     {
00425         // Tell the server to start the transfer
00426         Resp = SendCommand("STOR", Path + Filename);
00427         if (Resp.IsOk())
00428         {
00429             // Send the file data
00430             Data.Send(FileData);
00431 
00432             // Get the response from the server
00433             Resp = GetResponse();
00434         }
00435     }
00436 
00437     return Resp;
00438 }
00439 
00440 
00444 Ftp::Response Ftp::SendCommand(const std::string& Command, const std::string& Parameter)
00445 {
00446     // Build the command string
00447     std::string CommandStr;
00448     if (Parameter != "")
00449         CommandStr = Command + " " + Parameter + "\r\n";
00450     else
00451         CommandStr = Command + "\r\n";
00452 
00453     // Send it to the server
00454     if (myCommandSocket.Send(CommandStr.c_str(), CommandStr.length()) != sf::Socket::Done)
00455         return Response(Response::ConnectionClosed);
00456 
00457     // Get the response
00458     return GetResponse();
00459 }
00460 
00461 
00466 Ftp::Response Ftp::GetResponse()
00467 {
00468     // We'll use a variable to keep track of the last valid code.
00469     // It is useful in case of multi-lines responses, because the end of such a response
00470     // will start by the same code
00471     unsigned int LastCode  = 0;
00472     bool IsInsideMultiline = false;
00473     std::string Message;
00474 
00475     for (;;)
00476     {
00477         // Receive the response from the server
00478         char Buffer[1024];
00479         std::size_t Length;
00480         if (myCommandSocket.Receive(Buffer, sizeof(Buffer), Length) != sf::Socket::Done)
00481             return Response(Response::ConnectionClosed);
00482 
00483         // There can be several lines inside the received buffer, extract them all
00484         std::istringstream In(std::string(Buffer, Length), std::ios_base::binary);
00485         while (In)
00486         {
00487             // Try to extract the code
00488             unsigned int Code;
00489             if (In >> Code)
00490             {
00491                 // Extract the separator
00492                 char Sep;
00493                 In.get(Sep);
00494 
00495                 // The '-' character means a multiline response
00496                 if ((Sep == '-') && !IsInsideMultiline)
00497                 {
00498                     // Set the multiline flag
00499                     IsInsideMultiline = true;
00500 
00501                     // Keep track of the code
00502                     if (LastCode == 0)
00503                         LastCode = Code;
00504 
00505                     // Extract the line
00506                     std::getline(In, Message);
00507 
00508                     // Remove the ending '\r' (all lines are terminated by "\r\n")
00509                     Message.erase(Message.length() - 1);
00510                     Message = Sep + Message + "\n";
00511                 }
00512                 else
00513                 {
00514                     // We must make sure that the code is the same, otherwise it means
00515                     // we haven't reached the end of the multiline response
00516                     if ((Sep != '-') && ((Code == LastCode) || (LastCode == 0)))
00517                     {
00518                         // Clear the multiline flag
00519                         IsInsideMultiline = false;
00520 
00521                         // Extract the line
00522                         std::string Line;
00523                         std::getline(In, Line);
00524 
00525                         // Remove the ending '\r' (all lines are terminated by "\r\n")
00526                         Line.erase(Line.length() - 1);
00527 
00528                         // Append it to the message
00529                         if (Code == LastCode)
00530                         {
00531                             std::ostringstream Out;
00532                             Out << Code << Sep << Line;
00533                             Message += Out.str();
00534                         }
00535                         else
00536                         {
00537                             Message = Sep + Line;
00538                         }
00539 
00540                         // Return the response code and message
00541                         return Response(static_cast<Response::Status>(Code), Message);
00542                     }
00543                     else
00544                     {
00545                         // The line we just read was actually not a response,
00546                         // only a new part of the current multiline response
00547 
00548                         // Extract the line
00549                         std::string Line;
00550                         std::getline(In, Line);
00551 
00552                         if (!Line.empty())
00553                         {
00554                             // Remove the ending '\r' (all lines are terminated by "\r\n")
00555                             Line.erase(Line.length() - 1);
00556 
00557                             // Append it to the current message
00558                             std::ostringstream Out;
00559                             Out << Code << Sep << Line << "\n";
00560                             Message += Out.str();
00561                         }
00562                     }
00563                 }
00564             }
00565             else if (LastCode != 0)
00566             {
00567                 // It seems we are in the middle of a multiline response
00568 
00569                 // Clear the error bits of the stream
00570                 In.clear();
00571 
00572                 // Extract the line
00573                 std::string Line;
00574                 std::getline(In, Line);
00575 
00576                 if (!Line.empty())
00577                 {
00578                     // Remove the ending '\r' (all lines are terminated by "\r\n")
00579                     Line.erase(Line.length() - 1);
00580 
00581                     // Append it to the current message
00582                     Message += Line + "\n";
00583                 }
00584             }
00585             else
00586             {
00587                 // Error : cannot extract the code, and we are not in a multiline response
00588                 return Response(Response::InvalidResponse);
00589             }
00590         }
00591     }
00592 
00593     // We never reach there
00594 }
00595 
00596 
00600 Ftp::DataChannel::DataChannel(Ftp& Owner) :
00601 myFtp(Owner)
00602 {
00603 
00604 }
00605 
00606 
00610 Ftp::DataChannel::~DataChannel()
00611 {
00612     // Close the data socket
00613     myDataSocket.Close();
00614 }
00615 
00616 
00620 Ftp::Response Ftp::DataChannel::Open(Ftp::TransferMode Mode)
00621 {
00622     // Open a data connection in active mode (we connect to the server)
00623     Ftp::Response Resp = myFtp.SendCommand("PASV");
00624     if (Resp.IsOk())
00625     {
00626         // Extract the connection address and port from the response
00627         std::string::size_type begin = Resp.GetMessage().find_first_of("0123456789");
00628         if (begin != std::string::npos)
00629         {
00630             sf::Uint8 Data[6] = {0, 0, 0, 0, 0, 0};
00631             std::string Str = Resp.GetMessage().substr(begin);
00632             std::size_t Index = 0;
00633             for (int i = 0; i < 6; ++i)
00634             {
00635                 // Extract the current number
00636                 while (isdigit(Str[Index]))
00637                 {
00638                     Data[i] = Data[i] * 10 + (Str[Index] - '0');
00639                     Index++;
00640                 }
00641 
00642                 // Skip separator
00643                 Index++;
00644             }
00645 
00646             // Reconstruct connection port and address
00647             unsigned short Port = Data[4] * 256 + Data[5];
00648             sf::IPAddress Address(static_cast<sf::Uint8>(Data[0]),
00649                                   static_cast<sf::Uint8>(Data[1]),
00650                                   static_cast<sf::Uint8>(Data[2]),
00651                                   static_cast<sf::Uint8>(Data[3]));
00652 
00653             // Connect the data channel to the server
00654             if (myDataSocket.Connect(Port, Address) == Socket::Done)
00655             {
00656                 // Translate the transfer mode to the corresponding FTP parameter
00657                 std::string ModeStr;
00658                 switch (Mode)
00659                 {
00660                     case Ftp::Binary : ModeStr = "I"; break;
00661                     case Ftp::Ascii :  ModeStr = "A"; break;
00662                     case Ftp::Ebcdic : ModeStr = "E"; break;
00663                 }
00664 
00665                 // Set the transfer mode
00666                 Resp = myFtp.SendCommand("TYPE", ModeStr);
00667             }
00668             else
00669             {
00670                 // Failed to connect to the server
00671                 Resp = Ftp::Response(Ftp::Response::ConnectionFailed);
00672             }
00673         }
00674     }
00675 
00676     return Resp;
00677 }
00678 
00679 
00683 void Ftp::DataChannel::Receive(std::vector<char>& Data)
00684 {
00685     // Receive data
00686     Data.clear();
00687     char Buffer[1024];
00688     std::size_t Received;
00689     while (myDataSocket.Receive(Buffer, sizeof(Buffer), Received) == sf::Socket::Done)
00690     {
00691         std::copy(Buffer, Buffer + Received, std::back_inserter(Data));
00692     }
00693 
00694     // Close the data socket
00695     myDataSocket.Close();
00696 }
00697 
00698 
00702 void Ftp::DataChannel::Send(const std::vector<char>& Data)
00703 {
00704     // Send data
00705     if (!Data.empty())
00706         myDataSocket.Send(&Data[0], Data.size());
00707 
00708     // Close the data socket
00709     myDataSocket.Close();
00710 }
00711 
00712 } // namespace sf