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