Ftp.cpp
00001 00002 // 00003 // SFML - Simple and Fast Multimedia Library 00004 // Copyright (C) 2007-2009 Laurent Gomila ([email protected]) 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", "[email protected]"); 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
:: Copyright © 2007-2008 Laurent Gomila, all rights reserved :: Documentation generated by doxygen 1.5.2 ::