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 response) :
00124 Ftp::Response(response)
00125 {
00126     if (IsOk())
00127     {
00128         // Extract the directory from the server response
00129         std::string::size_type begin = response.GetMessage().find('"', 0);
00130         std::string::size_type end   = response.GetMessage().find('"', begin + 1);
00131         myDirectory = response.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 response, const std::vector<char>& data) :
00149 Ftp::Response(response)
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& name, const std::string& password)
00219 {
00220     Response response = SendCommand("USER", name);
00221     if (response.IsOk())
00222         response = SendCommand("PASS", password);
00223 
00224     return response;
00225 }
00226 
00227 
00231 Ftp::Response Ftp::Disconnect()
00232 {
00233     // Send the exit command
00234     Response response = SendCommand("QUIT");
00235     if (response.IsOk())
00236         myCommandSocket.Close();
00237 
00238     return response;
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> directoryData;
00268     DataChannel data(*this);
00269     Response response = data.Open(Ascii);
00270     if (response.IsOk())
00271     {
00272         // Tell the server to send us the listing
00273         response = SendCommand("NLST", directory);
00274         if (response.IsOk())
00275         {
00276             // Receive the listing
00277             data.Receive(directoryData);
00278 
00279             // Get the response from the server
00280             response = GetResponse();
00281         }
00282     }
00283 
00284     return ListingResponse(response, directoryData);
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 response = SendCommand("RNFR", file);
00330     if (response.IsOk())
00331        response = SendCommand("RNTO", newName);
00332 
00333     return response;
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 response = data.Open(mode);
00354     if (response.IsOk())
00355     {
00356         // Tell the server to start the transfer
00357         response = SendCommand("RETR", distantFile);
00358         if (response.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             response = GetResponse();
00366             if (response.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 
00384                 if (!fileData.empty())
00385                     file.write(&fileData[0], static_cast<std::streamsize>(fileData.size()));
00386             }
00387         }
00388     }
00389 
00390     return response;
00391 }
00392 
00393 
00397 Ftp::Response Ftp::Upload(const std::string& localFile, const std::string& destPath, TransferMode mode)
00398 {
00399     // Get the contents of the file to send
00400     std::ifstream file(localFile.c_str(), std::ios_base::binary);
00401     if (!file)
00402         return Response(Response::InvalidFile);
00403 
00404     file.seekg(0, std::ios::end);
00405     std::size_t length = file.tellg();
00406     file.seekg(0, std::ios::beg);
00407     std::vector<char> fileData(length);
00408     if (length > 0)
00409         file.read(&fileData[0], static_cast<std::streamsize>(length));
00410 
00411     // Extract the filename from the file path
00412     std::string filename = localFile;
00413     std::string::size_type pos = filename.find_last_of("/\\");
00414     if (pos != std::string::npos)
00415         filename = filename.substr(pos + 1);
00416 
00417     // Make sure the destination path ends with a slash
00418     std::string path = destPath;
00419     if (!path.empty() && (path[path.size() - 1] != '\\') && (path[path.size() - 1] != '/'))
00420         path += "/";
00421 
00422     // Open a data channel using the given transfer mode
00423     DataChannel data(*this);
00424     Response response = data.Open(mode);
00425     if (response.IsOk())
00426     {
00427         // Tell the server to start the transfer
00428         response = SendCommand("STOR", path + filename);
00429         if (response.IsOk())
00430         {
00431             // Send the file data
00432             data.Send(fileData);
00433 
00434             // Get the response from the server
00435             response = GetResponse();
00436         }
00437     }
00438 
00439     return response;
00440 }
00441 
00442 
00446 Ftp::Response Ftp::SendCommand(const std::string& command, const std::string& parameter)
00447 {
00448     // Build the command string
00449     std::string commandStr;
00450     if (parameter != "")
00451         commandStr = command + " " + parameter + "\r\n";
00452     else
00453         commandStr = command + "\r\n";
00454 
00455     // Send it to the server
00456     if (myCommandSocket.Send(commandStr.c_str(), commandStr.length()) != Socket::Done)
00457         return Response(Response::ConnectionClosed);
00458 
00459     // Get the response
00460     return GetResponse();
00461 }
00462 
00463 
00468 Ftp::Response Ftp::GetResponse()
00469 {
00470     // We'll use a variable to keep track of the last valid code.
00471     // It is useful in case of multi-lines responses, because the end of such a response
00472     // will start by the same code
00473     unsigned int lastCode  = 0;
00474     bool isInsideMultiline = false;
00475     std::string message;
00476 
00477     for (;;)
00478     {
00479         // Receive the response from the server
00480         char buffer[1024];
00481         std::size_t length;
00482         if (myCommandSocket.Receive(buffer, sizeof(buffer), length) != Socket::Done)
00483             return Response(Response::ConnectionClosed);
00484 
00485         // There can be several lines inside the received buffer, extract them all
00486         std::istringstream in(std::string(buffer, length), std::ios_base::binary);
00487         while (in)
00488         {
00489             // Try to extract the code
00490             unsigned int code;
00491             if (in >> code)
00492             {
00493                 // Extract the separator
00494                 char separator;
00495                 in.get(separator);
00496 
00497                 // The '-' character means a multiline response
00498                 if ((separator == '-') && !isInsideMultiline)
00499                 {
00500                     // Set the multiline flag
00501                     isInsideMultiline = true;
00502 
00503                     // Keep track of the code
00504                     if (lastCode == 0)
00505                         lastCode = code;
00506 
00507                     // Extract the line
00508                     std::getline(in, message);
00509 
00510                     // Remove the ending '\r' (all lines are terminated by "\r\n")
00511                     message.erase(message.length() - 1);
00512                     message = separator + message + "\n";
00513                 }
00514                 else
00515                 {
00516                     // We must make sure that the code is the same, otherwise it means
00517                     // we haven't reached the end of the multiline response
00518                     if ((separator != '-') && ((code == lastCode) || (lastCode == 0)))
00519                     {
00520                         // Clear the multiline flag
00521                         isInsideMultiline = false;
00522 
00523                         // Extract the line
00524                         std::string line;
00525                         std::getline(in, line);
00526 
00527                         // Remove the ending '\r' (all lines are terminated by "\r\n")
00528                         line.erase(line.length() - 1);
00529 
00530                         // Append it to the message
00531                         if (code == lastCode)
00532                         {
00533                             std::ostringstream out;
00534                             out << code << separator << line;
00535                             message += out.str();
00536                         }
00537                         else
00538                         {
00539                             message = separator + line;
00540                         }
00541 
00542                         // Return the response code and message
00543                         return Response(static_cast<Response::Status>(code), message);
00544                     }
00545                     else
00546                     {
00547                         // The line we just read was actually not a response,
00548                         // only a new part of the current multiline response
00549 
00550                         // Extract the line
00551                         std::string line;
00552                         std::getline(in, line);
00553 
00554                         if (!line.empty())
00555                         {
00556                             // Remove the ending '\r' (all lines are terminated by "\r\n")
00557                             line.erase(line.length() - 1);
00558 
00559                             // Append it to the current message
00560                             std::ostringstream out;
00561                             out << code << separator << line << "\n";
00562                             message += out.str();
00563                         }
00564                     }
00565                 }
00566             }
00567             else if (lastCode != 0)
00568             {
00569                 // It seems we are in the middle of a multiline response
00570 
00571                 // Clear the error bits of the stream
00572                 in.clear();
00573 
00574                 // Extract the line
00575                 std::string line;
00576                 std::getline(in, line);
00577 
00578                 if (!line.empty())
00579                 {
00580                     // Remove the ending '\r' (all lines are terminated by "\r\n")
00581                     line.erase(line.length() - 1);
00582 
00583                     // Append it to the current message
00584                     message += line + "\n";
00585                 }
00586             }
00587             else
00588             {
00589                 // Error : cannot extract the code, and we are not in a multiline response
00590                 return Response(Response::InvalidResponse);
00591             }
00592         }
00593     }
00594 
00595     // We never reach there
00596 }
00597 
00598 
00602 Ftp::DataChannel::DataChannel(Ftp& owner) :
00603 myFtp(owner)
00604 {
00605 
00606 }
00607 
00608 
00612 Ftp::DataChannel::~DataChannel()
00613 {
00614     // Close the data socket
00615     myDataSocket.Close();
00616 }
00617 
00618 
00622 Ftp::Response Ftp::DataChannel::Open(Ftp::TransferMode mode)
00623 {
00624     // Open a data connection in active mode (we connect to the server)
00625     Ftp::Response response = myFtp.SendCommand("PASV");
00626     if (response.IsOk())
00627     {
00628         // Extract the connection address and port from the response
00629         std::string::size_type begin = response.GetMessage().find_first_of("0123456789");
00630         if (begin != std::string::npos)
00631         {
00632             Uint8 data[6] = {0, 0, 0, 0, 0, 0};
00633             std::string str = response.GetMessage().substr(begin);
00634             std::size_t index = 0;
00635             for (int i = 0; i < 6; ++i)
00636             {
00637                 // Extract the current number
00638                 while (isdigit(str[index]))
00639                 {
00640                     data[i] = data[i] * 10 + (str[index] - '0');
00641                     index++;
00642                 }
00643 
00644                 // Skip separator
00645                 index++;
00646             }
00647 
00648             // Reconstruct connection port and address
00649             unsigned short port = data[4] * 256 + data[5];
00650             IPAddress address(static_cast<Uint8>(data[0]),
00651                               static_cast<Uint8>(data[1]),
00652                               static_cast<Uint8>(data[2]),
00653                               static_cast<Uint8>(data[3]));
00654 
00655             // Connect the data channel to the server
00656             if (myDataSocket.Connect(port, address) == Socket::Done)
00657             {
00658                 // Translate the transfer mode to the corresponding FTP parameter
00659                 std::string modeStr;
00660                 switch (mode)
00661                 {
00662                     case Ftp::Binary : modeStr = "I"; break;
00663                     case Ftp::Ascii :  modeStr = "A"; break;
00664                     case Ftp::Ebcdic : modeStr = "E"; break;
00665                 }
00666 
00667                 // Set the transfer mode
00668                 response = myFtp.SendCommand("TYPE", modeStr);
00669             }
00670             else
00671             {
00672                 // Failed to connect to the server
00673                 response = Ftp::Response(Ftp::Response::ConnectionFailed);
00674             }
00675         }
00676     }
00677 
00678     return response;
00679 }
00680 
00681 
00685 void Ftp::DataChannel::Receive(std::vector<char>& data)
00686 {
00687     // Receive data
00688     data.clear();
00689     char buffer[1024];
00690     std::size_t received;
00691     while (myDataSocket.Receive(buffer, sizeof(buffer), received) == Socket::Done)
00692     {
00693         std::copy(buffer, buffer + received, std::back_inserter(data));
00694     }
00695 
00696     // Close the data socket
00697     myDataSocket.Close();
00698 }
00699 
00700 
00704 void Ftp::DataChannel::Send(const std::vector<char>& data)
00705 {
00706     // Send data
00707     if (!data.empty())
00708         myDataSocket.Send(&data[0], data.size());
00709 
00710     // Close the data socket
00711     myDataSocket.Close();
00712 }
00713 
00714 } // namespace sf

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