PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/src/SFML/Network/Ftp.cpp

http://github.com/LaurentGomila/SFML
C++ | 653 lines | 410 code | 113 blank | 130 comment | 67 complexity | a8ffa8dca40bdf39d12fa9811c0d58f5 MD5 | raw file
  1. ////////////////////////////////////////////////////////////
  2. //
  3. // SFML - Simple and Fast Multimedia Library
  4. // Copyright (C) 2007-2019 Laurent Gomila (laurent@sfml-dev.org)
  5. //
  6. // This software is provided 'as-is', without any express or implied warranty.
  7. // In no event will the authors be held liable for any damages arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it freely,
  11. // subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented;
  14. // you must not claim that you wrote the original software.
  15. // If you use this software in a product, an acknowledgment
  16. // in the product documentation would be appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such,
  19. // and must not be misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source distribution.
  22. //
  23. ////////////////////////////////////////////////////////////
  24. ////////////////////////////////////////////////////////////
  25. // Headers
  26. ////////////////////////////////////////////////////////////
  27. #include <SFML/Network/Ftp.hpp>
  28. #include <SFML/Network/IpAddress.hpp>
  29. #include <SFML/System/Err.hpp>
  30. #include <algorithm>
  31. #include <cctype>
  32. #include <fstream>
  33. #include <iterator>
  34. #include <sstream>
  35. #include <cstdio>
  36. namespace sf
  37. {
  38. ////////////////////////////////////////////////////////////
  39. class Ftp::DataChannel : NonCopyable
  40. {
  41. public:
  42. ////////////////////////////////////////////////////////////
  43. DataChannel(Ftp& owner);
  44. ////////////////////////////////////////////////////////////
  45. Ftp::Response open(Ftp::TransferMode mode);
  46. ////////////////////////////////////////////////////////////
  47. void send(std::istream& stream);
  48. ////////////////////////////////////////////////////////////
  49. void receive(std::ostream& stream);
  50. private:
  51. ////////////////////////////////////////////////////////////
  52. // Member data
  53. ////////////////////////////////////////////////////////////
  54. Ftp& m_ftp; //!< Reference to the owner Ftp instance
  55. TcpSocket m_dataSocket; //!< Socket used for data transfers
  56. };
  57. ////////////////////////////////////////////////////////////
  58. Ftp::Response::Response(Status code, const std::string& message) :
  59. m_status (code),
  60. m_message(message)
  61. {
  62. }
  63. ////////////////////////////////////////////////////////////
  64. bool Ftp::Response::isOk() const
  65. {
  66. return m_status < 400;
  67. }
  68. ////////////////////////////////////////////////////////////
  69. Ftp::Response::Status Ftp::Response::getStatus() const
  70. {
  71. return m_status;
  72. }
  73. ////////////////////////////////////////////////////////////
  74. const std::string& Ftp::Response::getMessage() const
  75. {
  76. return m_message;
  77. }
  78. ////////////////////////////////////////////////////////////
  79. Ftp::DirectoryResponse::DirectoryResponse(const Ftp::Response& response) :
  80. Ftp::Response(response)
  81. {
  82. if (isOk())
  83. {
  84. // Extract the directory from the server response
  85. std::string::size_type begin = getMessage().find('"', 0);
  86. std::string::size_type end = getMessage().find('"', begin + 1);
  87. m_directory = getMessage().substr(begin + 1, end - begin - 1);
  88. }
  89. }
  90. ////////////////////////////////////////////////////////////
  91. const std::string& Ftp::DirectoryResponse::getDirectory() const
  92. {
  93. return m_directory;
  94. }
  95. ////////////////////////////////////////////////////////////
  96. Ftp::ListingResponse::ListingResponse(const Ftp::Response& response, const std::string& data) :
  97. Ftp::Response(response)
  98. {
  99. if (isOk())
  100. {
  101. // Fill the array of strings
  102. std::string::size_type lastPos = 0;
  103. for (std::string::size_type pos = data.find("\r\n"); pos != std::string::npos; pos = data.find("\r\n", lastPos))
  104. {
  105. m_listing.push_back(data.substr(lastPos, pos - lastPos));
  106. lastPos = pos + 2;
  107. }
  108. }
  109. }
  110. ////////////////////////////////////////////////////////////
  111. const std::vector<std::string>& Ftp::ListingResponse::getListing() const
  112. {
  113. return m_listing;
  114. }
  115. ////////////////////////////////////////////////////////////
  116. Ftp::~Ftp()
  117. {
  118. disconnect();
  119. }
  120. ////////////////////////////////////////////////////////////
  121. Ftp::Response Ftp::connect(const IpAddress& server, unsigned short port, Time timeout)
  122. {
  123. // Connect to the server
  124. if (m_commandSocket.connect(server, port, timeout) != Socket::Done)
  125. return Response(Response::ConnectionFailed);
  126. // Get the response to the connection
  127. return getResponse();
  128. }
  129. ////////////////////////////////////////////////////////////
  130. Ftp::Response Ftp::login()
  131. {
  132. return login("anonymous", "user@sfml-dev.org");
  133. }
  134. ////////////////////////////////////////////////////////////
  135. Ftp::Response Ftp::login(const std::string& name, const std::string& password)
  136. {
  137. Response response = sendCommand("USER", name);
  138. if (response.isOk())
  139. response = sendCommand("PASS", password);
  140. return response;
  141. }
  142. ////////////////////////////////////////////////////////////
  143. Ftp::Response Ftp::disconnect()
  144. {
  145. // Send the exit command
  146. Response response = sendCommand("QUIT");
  147. if (response.isOk())
  148. m_commandSocket.disconnect();
  149. return response;
  150. }
  151. ////////////////////////////////////////////////////////////
  152. Ftp::Response Ftp::keepAlive()
  153. {
  154. return sendCommand("NOOP");
  155. }
  156. ////////////////////////////////////////////////////////////
  157. Ftp::DirectoryResponse Ftp::getWorkingDirectory()
  158. {
  159. return DirectoryResponse(sendCommand("PWD"));
  160. }
  161. ////////////////////////////////////////////////////////////
  162. Ftp::ListingResponse Ftp::getDirectoryListing(const std::string& directory)
  163. {
  164. // Open a data channel on default port (20) using ASCII transfer mode
  165. std::ostringstream directoryData;
  166. DataChannel data(*this);
  167. Response response = data.open(Ascii);
  168. if (response.isOk())
  169. {
  170. // Tell the server to send us the listing
  171. response = sendCommand("NLST", directory);
  172. if (response.isOk())
  173. {
  174. // Receive the listing
  175. data.receive(directoryData);
  176. // Get the response from the server
  177. response = getResponse();
  178. }
  179. }
  180. return ListingResponse(response, directoryData.str());
  181. }
  182. ////////////////////////////////////////////////////////////
  183. Ftp::Response Ftp::changeDirectory(const std::string& directory)
  184. {
  185. return sendCommand("CWD", directory);
  186. }
  187. ////////////////////////////////////////////////////////////
  188. Ftp::Response Ftp::parentDirectory()
  189. {
  190. return sendCommand("CDUP");
  191. }
  192. ////////////////////////////////////////////////////////////
  193. Ftp::Response Ftp::createDirectory(const std::string& name)
  194. {
  195. return sendCommand("MKD", name);
  196. }
  197. ////////////////////////////////////////////////////////////
  198. Ftp::Response Ftp::deleteDirectory(const std::string& name)
  199. {
  200. return sendCommand("RMD", name);
  201. }
  202. ////////////////////////////////////////////////////////////
  203. Ftp::Response Ftp::renameFile(const std::string& file, const std::string& newName)
  204. {
  205. Response response = sendCommand("RNFR", file);
  206. if (response.isOk())
  207. response = sendCommand("RNTO", newName);
  208. return response;
  209. }
  210. ////////////////////////////////////////////////////////////
  211. Ftp::Response Ftp::deleteFile(const std::string& name)
  212. {
  213. return sendCommand("DELE", name);
  214. }
  215. ////////////////////////////////////////////////////////////
  216. Ftp::Response Ftp::download(const std::string& remoteFile, const std::string& localPath, TransferMode mode)
  217. {
  218. // Open a data channel using the given transfer mode
  219. DataChannel data(*this);
  220. Response response = data.open(mode);
  221. if (response.isOk())
  222. {
  223. // Tell the server to start the transfer
  224. response = sendCommand("RETR", remoteFile);
  225. if (response.isOk())
  226. {
  227. // Extract the filename from the file path
  228. std::string filename = remoteFile;
  229. std::string::size_type pos = filename.find_last_of("/\\");
  230. if (pos != std::string::npos)
  231. filename = filename.substr(pos + 1);
  232. // Make sure the destination path ends with a slash
  233. std::string path = localPath;
  234. if (!path.empty() && (path[path.size() - 1] != '\\') && (path[path.size() - 1] != '/'))
  235. path += "/";
  236. // Create the file and truncate it if necessary
  237. std::ofstream file((path + filename).c_str(), std::ios_base::binary | std::ios_base::trunc);
  238. if (!file)
  239. return Response(Response::InvalidFile);
  240. // Receive the file data
  241. data.receive(file);
  242. // Close the file
  243. file.close();
  244. // Get the response from the server
  245. response = getResponse();
  246. // If the download was unsuccessful, delete the partial file
  247. if (!response.isOk())
  248. std::remove((path + filename).c_str());
  249. }
  250. }
  251. return response;
  252. }
  253. ////////////////////////////////////////////////////////////
  254. Ftp::Response Ftp::upload(const std::string& localFile, const std::string& remotePath, TransferMode mode, bool append)
  255. {
  256. // Get the contents of the file to send
  257. std::ifstream file(localFile.c_str(), std::ios_base::binary);
  258. if (!file)
  259. return Response(Response::InvalidFile);
  260. // Extract the filename from the file path
  261. std::string filename = localFile;
  262. std::string::size_type pos = filename.find_last_of("/\\");
  263. if (pos != std::string::npos)
  264. filename = filename.substr(pos + 1);
  265. // Make sure the destination path ends with a slash
  266. std::string path = remotePath;
  267. if (!path.empty() && (path[path.size() - 1] != '\\') && (path[path.size() - 1] != '/'))
  268. path += "/";
  269. // Open a data channel using the given transfer mode
  270. DataChannel data(*this);
  271. Response response = data.open(mode);
  272. if (response.isOk())
  273. {
  274. // Tell the server to start the transfer
  275. response = sendCommand(append ? "APPE" : "STOR", path + filename);
  276. if (response.isOk())
  277. {
  278. // Send the file data
  279. data.send(file);
  280. // Get the response from the server
  281. response = getResponse();
  282. }
  283. }
  284. return response;
  285. }
  286. ////////////////////////////////////////////////////////////
  287. Ftp::Response Ftp::sendCommand(const std::string& command, const std::string& parameter)
  288. {
  289. // Build the command string
  290. std::string commandStr;
  291. if (parameter != "")
  292. commandStr = command + " " + parameter + "\r\n";
  293. else
  294. commandStr = command + "\r\n";
  295. // Send it to the server
  296. if (m_commandSocket.send(commandStr.c_str(), commandStr.length()) != Socket::Done)
  297. return Response(Response::ConnectionClosed);
  298. // Get the response
  299. return getResponse();
  300. }
  301. ////////////////////////////////////////////////////////////
  302. Ftp::Response Ftp::getResponse()
  303. {
  304. // We'll use a variable to keep track of the last valid code.
  305. // It is useful in case of multi-lines responses, because the end of such a response
  306. // will start by the same code
  307. unsigned int lastCode = 0;
  308. bool isInsideMultiline = false;
  309. std::string message;
  310. for (;;)
  311. {
  312. // Receive the response from the server
  313. char buffer[1024];
  314. std::size_t length;
  315. if (m_receiveBuffer.empty())
  316. {
  317. if (m_commandSocket.receive(buffer, sizeof(buffer), length) != Socket::Done)
  318. return Response(Response::ConnectionClosed);
  319. }
  320. else
  321. {
  322. std::copy(m_receiveBuffer.begin(), m_receiveBuffer.end(), buffer);
  323. length = m_receiveBuffer.size();
  324. m_receiveBuffer.clear();
  325. }
  326. // There can be several lines inside the received buffer, extract them all
  327. std::istringstream in(std::string(buffer, length), std::ios_base::binary);
  328. while (in)
  329. {
  330. // Try to extract the code
  331. unsigned int code;
  332. if (in >> code)
  333. {
  334. // Extract the separator
  335. char separator;
  336. in.get(separator);
  337. // The '-' character means a multiline response
  338. if ((separator == '-') && !isInsideMultiline)
  339. {
  340. // Set the multiline flag
  341. isInsideMultiline = true;
  342. // Keep track of the code
  343. if (lastCode == 0)
  344. lastCode = code;
  345. // Extract the line
  346. std::getline(in, message);
  347. // Remove the ending '\r' (all lines are terminated by "\r\n")
  348. message.erase(message.length() - 1);
  349. message = separator + message + "\n";
  350. }
  351. else
  352. {
  353. // We must make sure that the code is the same, otherwise it means
  354. // we haven't reached the end of the multiline response
  355. if ((separator != '-') && ((code == lastCode) || (lastCode == 0)))
  356. {
  357. // Extract the line
  358. std::string line;
  359. std::getline(in, line);
  360. // Remove the ending '\r' (all lines are terminated by "\r\n")
  361. line.erase(line.length() - 1);
  362. // Append it to the message
  363. if (code == lastCode)
  364. {
  365. std::ostringstream out;
  366. out << code << separator << line;
  367. message += out.str();
  368. }
  369. else
  370. {
  371. message = separator + line;
  372. }
  373. // Save the remaining data for the next time getResponse() is called
  374. m_receiveBuffer.assign(buffer + static_cast<std::size_t>(in.tellg()), length - static_cast<std::size_t>(in.tellg()));
  375. // Return the response code and message
  376. return Response(static_cast<Response::Status>(code), message);
  377. }
  378. else
  379. {
  380. // The line we just read was actually not a response,
  381. // only a new part of the current multiline response
  382. // Extract the line
  383. std::string line;
  384. std::getline(in, line);
  385. if (!line.empty())
  386. {
  387. // Remove the ending '\r' (all lines are terminated by "\r\n")
  388. line.erase(line.length() - 1);
  389. // Append it to the current message
  390. std::ostringstream out;
  391. out << code << separator << line << "\n";
  392. message += out.str();
  393. }
  394. }
  395. }
  396. }
  397. else if (lastCode != 0)
  398. {
  399. // It seems we are in the middle of a multiline response
  400. // Clear the error bits of the stream
  401. in.clear();
  402. // Extract the line
  403. std::string line;
  404. std::getline(in, line);
  405. if (!line.empty())
  406. {
  407. // Remove the ending '\r' (all lines are terminated by "\r\n")
  408. line.erase(line.length() - 1);
  409. // Append it to the current message
  410. message += line + "\n";
  411. }
  412. }
  413. else
  414. {
  415. // Error: cannot extract the code, and we are not in a multiline response
  416. return Response(Response::InvalidResponse);
  417. }
  418. }
  419. }
  420. // We never reach there
  421. }
  422. ////////////////////////////////////////////////////////////
  423. Ftp::DataChannel::DataChannel(Ftp& owner) :
  424. m_ftp(owner)
  425. {
  426. }
  427. ////////////////////////////////////////////////////////////
  428. Ftp::Response Ftp::DataChannel::open(Ftp::TransferMode mode)
  429. {
  430. // Open a data connection in active mode (we connect to the server)
  431. Ftp::Response response = m_ftp.sendCommand("PASV");
  432. if (response.isOk())
  433. {
  434. // Extract the connection address and port from the response
  435. std::string::size_type begin = response.getMessage().find_first_of("0123456789");
  436. if (begin != std::string::npos)
  437. {
  438. Uint8 data[6] = {0, 0, 0, 0, 0, 0};
  439. std::string str = response.getMessage().substr(begin);
  440. std::size_t index = 0;
  441. for (int i = 0; i < 6; ++i)
  442. {
  443. // Extract the current number
  444. while (isdigit(str[index]))
  445. {
  446. data[i] = data[i] * 10 + (str[index] - '0');
  447. index++;
  448. }
  449. // Skip separator
  450. index++;
  451. }
  452. // Reconstruct connection port and address
  453. unsigned short port = data[4] * 256 + data[5];
  454. IpAddress address(static_cast<Uint8>(data[0]),
  455. static_cast<Uint8>(data[1]),
  456. static_cast<Uint8>(data[2]),
  457. static_cast<Uint8>(data[3]));
  458. // Connect the data channel to the server
  459. if (m_dataSocket.connect(address, port) == Socket::Done)
  460. {
  461. // Translate the transfer mode to the corresponding FTP parameter
  462. std::string modeStr;
  463. switch (mode)
  464. {
  465. case Ftp::Binary: modeStr = "I"; break;
  466. case Ftp::Ascii: modeStr = "A"; break;
  467. case Ftp::Ebcdic: modeStr = "E"; break;
  468. }
  469. // Set the transfer mode
  470. response = m_ftp.sendCommand("TYPE", modeStr);
  471. }
  472. else
  473. {
  474. // Failed to connect to the server
  475. response = Ftp::Response(Ftp::Response::ConnectionFailed);
  476. }
  477. }
  478. }
  479. return response;
  480. }
  481. ////////////////////////////////////////////////////////////
  482. void Ftp::DataChannel::receive(std::ostream& stream)
  483. {
  484. // Receive data
  485. char buffer[1024];
  486. std::size_t received;
  487. while (m_dataSocket.receive(buffer, sizeof(buffer), received) == Socket::Done)
  488. {
  489. stream.write(buffer, static_cast<std::streamsize>(received));
  490. if (!stream.good())
  491. {
  492. err() << "FTP Error: Writing to the file has failed" << std::endl;
  493. break;
  494. }
  495. }
  496. // Close the data socket
  497. m_dataSocket.disconnect();
  498. }
  499. ////////////////////////////////////////////////////////////
  500. void Ftp::DataChannel::send(std::istream& stream)
  501. {
  502. // Send data
  503. char buffer[1024];
  504. std::size_t count;
  505. for (;;)
  506. {
  507. // read some data from the stream
  508. stream.read(buffer, sizeof(buffer));
  509. if (!stream.good() && !stream.eof())
  510. {
  511. err() << "FTP Error: Reading from the file has failed" << std::endl;
  512. break;
  513. }
  514. count = static_cast<std::size_t>(stream.gcount());
  515. if (count > 0)
  516. {
  517. // we could read more data from the stream: send them
  518. if (m_dataSocket.send(buffer, count) != Socket::Done)
  519. break;
  520. }
  521. else
  522. {
  523. // no more data: exit the loop
  524. break;
  525. }
  526. }
  527. // Close the data socket
  528. m_dataSocket.disconnect();
  529. }
  530. } // namespace sf