SFML logo
  • Main Page
  • Namespaces
  • Classes
  • Files
  • File List

Ftp.cpp

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                 File.write(&FileData[0], static_cast<std::streamsize>(FileData.size()));
00384             }
00385         }
00386     }
00387 
00388     return Resp;
00389 }
00390 
00391 
00395 Ftp::Response Ftp::Upload(const std::string& LocalFile, const std::string& DestPath, TransferMode Mode)
00396 {
00397     // Get the contents of the file to send
00398     std::ifstream File(LocalFile.c_str(), std::ios_base::binary);
00399     if (!File)
00400         return Response(Response::InvalidFile);
00401     File.seekg(0, std::ios::end);
00402     std::size_t Length = File.tellg();
00403     File.seekg(0, std::ios::beg);
00404     std::vector<char> FileData(Length);
00405     File.read(&FileData[0], static_cast<std::streamsize>(Length));
00406 
00407     // Extract the filename from the file path
00408     std::string Filename = LocalFile;
00409     std::string::size_type Pos = Filename.find_last_of("/\\");
00410     if (Pos != std::string::npos)
00411         Filename = Filename.substr(Pos + 1);
00412 
00413     // Make sure the destination path ends with a slash
00414     std::string Path = DestPath;
00415     if (!Path.empty() && (Path[Path.size() - 1] != '\\') && (Path[Path.size() - 1] != '/'))
00416         Path += "/";
00417 
00418     // Open a data channel using the given transfer mode
00419     DataChannel Data(*this);
00420     Response Resp = Data.Open(Mode);
00421     if (Resp.IsOk())
00422     {
00423         // Tell the server to start the transfer
00424         Resp = SendCommand("STOR", Path + Filename);
00425         if (Resp.IsOk())
00426         {
00427             // Send the file data
00428             Data.Send(FileData);
00429 
00430             // Get the response from the server
00431             Resp = GetResponse();
00432         }
00433     }
00434 
00435     return Resp;
00436 }
00437 
00438 
00442 Ftp::Response Ftp::SendCommand(const std::string& Command, const std::string& Parameter)
00443 {
00444     // Build the command string
00445     std::string CommandStr;
00446     if (Parameter != "")
00447         CommandStr = Command + " " + Parameter + "\r\n";
00448     else
00449         CommandStr = Command + "\r\n";
00450 
00451     // Send it to the server
00452     if (myCommandSocket.Send(CommandStr.c_str(), CommandStr.length()) != sf::Socket::Done)
00453         return Response(Response::ConnectionClosed);
00454 
00455     // Get the response
00456     return GetResponse();
00457 }
00458 
00459 
00464 Ftp::Response Ftp::GetResponse()
00465 {
00466     // We'll use a variable to keep track of the last valid code.
00467     // It is useful in case of multi-lines responses, because the end of such a response
00468     // will start by the same code
00469     unsigned int LastCode  = 0;
00470     bool IsInsideMultiline = false;
00471     std::string Message;
00472 
00473     for (;;)
00474     {
00475         // Receive the response from the server
00476         char Buffer[1024];
00477         std::size_t Length;
00478         if (myCommandSocket.Receive(Buffer, sizeof(Buffer), Length) != sf::Socket::Done)
00479             return Response(Response::ConnectionClosed);
00480 
00481         // There can be several lines inside the received buffer, extract them all
00482         std::istringstream In(std::string(Buffer, Length), std::ios_base::binary);
00483         while (In)
00484         {
00485             // Try to extract the code
00486             unsigned int Code;
00487             if (In >> Code)
00488             {
00489                 // Extract the separator
00490                 char Sep;
00491                 In.get(Sep);
00492 
00493                 // The '-' character means a multiline response
00494                 if ((Sep == '-') && !IsInsideMultiline)
00495                 {
00496                     // Set the multiline flag
00497                     IsInsideMultiline = true;
00498 
00499                     // Keep track of the code
00500                     if (LastCode == 0)
00501                         LastCode = Code;
00502 
00503                     // Extract the line
00504                     std::getline(In, Message);
00505 
00506                     // Remove the ending '\r' (all lines are terminated by "\r\n")
00507                     Message.erase(Message.length() - 1);
00508                     Message = Sep + Message + "\n";
00509                 }
00510                 else
00511                 {
00512                     // We must make sure that the code is the same, otherwise it means
00513                     // we haven't reached the end of the multiline response
00514                     if ((Sep != '-') && ((Code == LastCode) || (LastCode == 0)))
00515                     {
00516                         // Clear the multiline flag
00517                         IsInsideMultiline = false;
00518 
00519                         // Extract the line
00520                         std::string Line;
00521                         std::getline(In, Line);
00522 
00523                         // Remove the ending '\r' (all lines are terminated by "\r\n")
00524                         Line.erase(Line.length() - 1);
00525 
00526                         // Append it to the message
00527                         if (Code == LastCode)
00528                         {
00529                             std::ostringstream Out;
00530                             Out << Code << Sep << Line;
00531                             Message += Out.str();
00532                         }
00533                         else
00534                         {
00535                             Message = Sep + Line;
00536                         }
00537 
00538                         // Return the response code and message
00539                         return Response(static_cast<Response::Status>(Code), Message);
00540                     }
00541                     else
00542                     {
00543                         // The line we just read was actually not a response,
00544                         // only a new part of the current multiline response
00545 
00546                         // Extract the line
00547                         std::string Line;
00548                         std::getline(In, Line);
00549 
00550                         if (!Line.empty())
00551                         {
00552                             // Remove the ending '\r' (all lines are terminated by "\r\n")
00553                             Line.erase(Line.length() - 1);
00554 
00555                             // Append it to the current message
00556                             std::ostringstream Out;
00557                             Out << Code << Sep << Line << "\n";
00558                             Message += Out.str();
00559                         }
00560                     }
00561                 }
00562             }
00563             else if (LastCode != 0)
00564             {
00565                 // It seems we are in the middle of a multiline response
00566 
00567                 // Clear the error bits of the stream
00568                 In.clear();
00569 
00570                 // Extract the line
00571                 std::string Line;
00572                 std::getline(In, Line);
00573 
00574                 if (!Line.empty())
00575                 {
00576                     // Remove the ending '\r' (all lines are terminated by "\r\n")
00577                     Line.erase(Line.length() - 1);
00578 
00579                     // Append it to the current message
00580                     Message += Line + "\n";
00581                 }
00582             }
00583             else
00584             {
00585                 // Error : cannot extract the code, and we are not in a multiline response
00586                 return Response(Response::InvalidResponse);
00587             }
00588         }
00589     }
00590 
00591     // We never reach there
00592 }
00593 
00594 
00598 Ftp::DataChannel::DataChannel(Ftp& Owner) :
00599 myFtp(Owner)
00600 {
00601 
00602 }
00603 
00604 
00608 Ftp::DataChannel::~DataChannel()
00609 {
00610     // Close the data socket
00611     myDataSocket.Close();
00612 }
00613 
00614 
00618 Ftp::Response Ftp::DataChannel::Open(Ftp::TransferMode Mode)
00619 {
00620     // Open a data connection in active mode (we connect to the server)
00621     Ftp::Response Resp = myFtp.SendCommand("PASV");
00622     if (Resp.IsOk())
00623     {
00624         // Extract the connection address and port from the response
00625         std::string::size_type begin = Resp.GetMessage().find_first_of("0123456789");
00626         if (begin != std::string::npos)
00627         {
00628             sf::Uint8 Data[6] = {0, 0, 0, 0, 0, 0};
00629             std::string Str = Resp.GetMessage().substr(begin);
00630             std::size_t Index = 0;
00631             for (int i = 0; i < 6; ++i)
00632             {
00633                 // Extract the current number
00634                 while (isdigit(Str[Index]))
00635                 {
00636                     Data[i] = Data[i] * 10 + (Str[Index] - '0');
00637                     Index++;
00638                 }
00639 
00640                 // Skip separator
00641                 Index++;
00642             }
00643 
00644             // Reconstruct connection port and address
00645             unsigned short Port = Data[4] * 256 + Data[5];
00646             sf::IPAddress Address(static_cast<sf::Uint8>(Data[0]),
00647                                   static_cast<sf::Uint8>(Data[1]),
00648                                   static_cast<sf::Uint8>(Data[2]),
00649                                   static_cast<sf::Uint8>(Data[3]));
00650 
00651             // Connect the data channel to the server
00652             if (myDataSocket.Connect(Port, Address) == Socket::Done)
00653             {
00654                 // Translate the transfer mode to the corresponding FTP parameter
00655                 std::string ModeStr;
00656                 switch (Mode)
00657                 {
00658                     case Ftp::Binary : ModeStr = "I"; break;
00659                     case Ftp::Ascii :  ModeStr = "A"; break;
00660                     case Ftp::Ebcdic : ModeStr = "E"; break;
00661                 }
00662 
00663                 // Set the transfer mode
00664                 Resp = myFtp.SendCommand("TYPE", ModeStr);
00665             }
00666             else
00667             {
00668                 // Failed to connect to the server
00669                 Resp = Ftp::Response(Ftp::Response::ConnectionFailed);
00670             }
00671         }
00672     }
00673 
00674     return Resp;
00675 }
00676 
00677 
00681 void Ftp::DataChannel::Receive(std::vector<char>& Data)
00682 {
00683     // Receive data
00684     Data.clear();
00685     char Buffer[1024];
00686     std::size_t Received;
00687     while (myDataSocket.Receive(Buffer, sizeof(Buffer), Received) == sf::Socket::Done)
00688     {
00689         std::copy(Buffer, Buffer + Received, std::back_inserter(Data));
00690     }
00691 
00692     // Close the data socket
00693     myDataSocket.Close();
00694 }
00695 
00696 
00700 void Ftp::DataChannel::Send(const std::vector<char>& Data)
00701 {
00702     // Send data
00703     myDataSocket.Send(&Data[0], Data.size());
00704 
00705     // Close the data socket
00706     myDataSocket.Close();
00707 }
00708 
00709 } // namespace sf

 ::  Copyright © 2007-2008 Laurent Gomila, all rights reserved  ::  Documentation generated by doxygen 1.5.2  ::