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 ::