1 | // |
2 | // FTPClientSession.cpp |
3 | // |
4 | // Library: Net |
5 | // Package: FTP |
6 | // Module: FTPClientSession |
7 | // |
8 | // Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Net/FTPClientSession.h" |
16 | #include "Poco/Net/SocketAddress.h" |
17 | #include "Poco/Net/SocketStream.h" |
18 | #include "Poco/Net/ServerSocket.h" |
19 | #include "Poco/Net/NetException.h" |
20 | #include "Poco/NumberFormatter.h" |
21 | #include "Poco/Ascii.h" |
22 | |
23 | |
24 | using Poco::NumberFormatter; |
25 | |
26 | |
27 | namespace Poco { |
28 | namespace Net { |
29 | |
30 | |
31 | FTPClientSession::FTPClientSession(): |
32 | _pControlSocket(0), |
33 | _pDataStream(0), |
34 | _port(0), |
35 | _passiveMode(true), |
36 | _fileType(TYPE_BINARY), |
37 | _supports1738(true), |
38 | _serverReady(false), |
39 | _isLoggedIn(false), |
40 | _timeout(DEFAULT_TIMEOUT) |
41 | { |
42 | } |
43 | |
44 | |
45 | FTPClientSession::FTPClientSession(const StreamSocket& socket): |
46 | _pControlSocket(new DialogSocket(socket)), |
47 | _pDataStream(0), |
48 | _host(socket.address().host().toString()), |
49 | _port(socket.address().port()), |
50 | _passiveMode(true), |
51 | _fileType(TYPE_BINARY), |
52 | _supports1738(true), |
53 | _serverReady(false), |
54 | _isLoggedIn(false), |
55 | _timeout(DEFAULT_TIMEOUT) |
56 | { |
57 | _pControlSocket->setReceiveTimeout(_timeout); |
58 | } |
59 | |
60 | |
61 | FTPClientSession::FTPClientSession(const std::string& host, |
62 | Poco::UInt16 port, |
63 | const std::string& username, |
64 | const std::string& password): |
65 | _pControlSocket(new DialogSocket(SocketAddress(host, port))), |
66 | _pDataStream(0), |
67 | _host(host), |
68 | _port(port), |
69 | _passiveMode(true), |
70 | _fileType(TYPE_BINARY), |
71 | _supports1738(true), |
72 | _serverReady(false), |
73 | _isLoggedIn(false), |
74 | _timeout(DEFAULT_TIMEOUT) |
75 | { |
76 | _pControlSocket->setReceiveTimeout(_timeout); |
77 | if (!username.empty()) |
78 | login(username, password); |
79 | } |
80 | |
81 | |
82 | FTPClientSession::~FTPClientSession() |
83 | { |
84 | try |
85 | { |
86 | close(); |
87 | } |
88 | catch (...) |
89 | { |
90 | } |
91 | } |
92 | |
93 | void FTPClientSession::setTimeout(const Poco::Timespan& timeout) |
94 | { |
95 | if (!isOpen()) |
96 | throw FTPException("Connection is closed." ); |
97 | |
98 | _timeout = timeout; |
99 | _pControlSocket->setReceiveTimeout(timeout); |
100 | } |
101 | |
102 | |
103 | Poco::Timespan FTPClientSession::getTimeout() const |
104 | { |
105 | return _timeout; |
106 | } |
107 | |
108 | |
109 | void FTPClientSession::setPassive(bool flag, bool useRFC1738) |
110 | { |
111 | _passiveMode = flag; |
112 | _supports1738 = useRFC1738; |
113 | } |
114 | |
115 | |
116 | bool FTPClientSession::getPassive() const |
117 | { |
118 | return _passiveMode; |
119 | } |
120 | |
121 | |
122 | void FTPClientSession::open(const std::string& host, |
123 | Poco::UInt16 port, |
124 | const std::string& username, |
125 | const std::string& password) |
126 | { |
127 | _host = host; |
128 | _port = port; |
129 | if (!username.empty()) |
130 | { |
131 | login(username, password); |
132 | } |
133 | else |
134 | { |
135 | if (!_pControlSocket) |
136 | { |
137 | _pControlSocket = new DialogSocket(SocketAddress(_host, _port)); |
138 | _pControlSocket->setReceiveTimeout(_timeout); |
139 | } |
140 | receiveServerReadyReply(); |
141 | } |
142 | } |
143 | |
144 | void FTPClientSession::receiveServerReadyReply() |
145 | { |
146 | if (_serverReady) |
147 | return; |
148 | std::string response; |
149 | int status = _pControlSocket->receiveStatusMessage(response); |
150 | if (!isPositiveCompletion(status)) |
151 | throw FTPException("Cannot receive status message" , response, status); |
152 | |
153 | _serverReady = true; |
154 | } |
155 | |
156 | void FTPClientSession::login(const std::string& username, const std::string& password) |
157 | { |
158 | if (_isLoggedIn) logout(); |
159 | |
160 | int status = FTP_POSITIVE_COMPLETION * 100; |
161 | std::string response; |
162 | if (!_pControlSocket) |
163 | { |
164 | _pControlSocket = new DialogSocket(SocketAddress(_host, _port)); |
165 | _pControlSocket->setReceiveTimeout(_timeout); |
166 | } |
167 | receiveServerReadyReply(); |
168 | |
169 | status = sendCommand("USER" , username, response); |
170 | if (isPositiveIntermediate(status)) |
171 | status = sendCommand("PASS" , password, response); |
172 | if (!isPositiveCompletion(status)) |
173 | throw FTPException("Login denied" , response, status); |
174 | |
175 | setFileType(_fileType); |
176 | _isLoggedIn = true; |
177 | } |
178 | |
179 | |
180 | void FTPClientSession::logout() |
181 | { |
182 | if (!isOpen()) |
183 | throw FTPException("Connection is closed." ); |
184 | |
185 | if (_isLoggedIn) |
186 | { |
187 | try { endTransfer(); } |
188 | catch (...) { } |
189 | _isLoggedIn = false; |
190 | std::string response; |
191 | sendCommand("QUIT" , response); |
192 | } |
193 | } |
194 | |
195 | |
196 | void FTPClientSession::close() |
197 | { |
198 | try { logout(); } |
199 | catch(...){ } |
200 | _serverReady = false; |
201 | if (_pControlSocket) |
202 | { |
203 | _pControlSocket->close(); |
204 | delete _pControlSocket; |
205 | _pControlSocket = 0; |
206 | } |
207 | } |
208 | |
209 | |
210 | void FTPClientSession::setFileType(FTPClientSession::FileType type) |
211 | { |
212 | std::string response; |
213 | int status = sendCommand("TYPE" , (type == TYPE_TEXT ? "A" : "I" ), response); |
214 | if (!isPositiveCompletion(status)) throw FTPException("Cannot set file type" , response, status); |
215 | _fileType = type; |
216 | } |
217 | |
218 | |
219 | FTPClientSession::FileType FTPClientSession::getFileType() const |
220 | { |
221 | return _fileType; |
222 | } |
223 | |
224 | |
225 | std::string FTPClientSession::systemType() |
226 | { |
227 | std::string response; |
228 | int status = sendCommand("SYST" , response); |
229 | if (isPositiveCompletion(status)) |
230 | return response.substr(4); |
231 | else |
232 | throw FTPException("Cannot get remote system type" , response, status); |
233 | } |
234 | |
235 | |
236 | void FTPClientSession::setWorkingDirectory(const std::string& path) |
237 | { |
238 | std::string response; |
239 | int status = sendCommand("CWD" , path, response); |
240 | if (!isPositiveCompletion(status)) |
241 | throw FTPException("Cannot change directory" , response, status); |
242 | } |
243 | |
244 | |
245 | std::string FTPClientSession::getWorkingDirectory() |
246 | { |
247 | std::string response; |
248 | int status = sendCommand("PWD" , response); |
249 | if (isPositiveCompletion(status)) |
250 | return extractPath(response); |
251 | else |
252 | throw FTPException("Cannot get current working directory" , response, status); |
253 | } |
254 | |
255 | |
256 | void FTPClientSession::cdup() |
257 | { |
258 | std::string response; |
259 | int status = sendCommand("CDUP" , response); |
260 | if (!isPositiveCompletion(status)) |
261 | throw FTPException("Cannot change directory" , response, status); |
262 | } |
263 | |
264 | |
265 | void FTPClientSession::rename(const std::string& oldName, const std::string& newName) |
266 | { |
267 | std::string response; |
268 | int status = sendCommand("RNFR" , oldName, response); |
269 | if (!isPositiveIntermediate(status)) |
270 | throw FTPException(std::string("Cannot rename " ) + oldName, response, status); |
271 | status = sendCommand("RNTO" , newName, response); |
272 | if (!isPositiveCompletion(status)) |
273 | throw FTPException(std::string("Cannot rename to " ) + newName, response, status); |
274 | } |
275 | |
276 | |
277 | void FTPClientSession::remove(const std::string& path) |
278 | { |
279 | std::string response; |
280 | int status = sendCommand("DELE" , path, response); |
281 | if (!isPositiveCompletion(status)) |
282 | throw FTPException(std::string("Cannot remove " + path), response, status); |
283 | } |
284 | |
285 | |
286 | void FTPClientSession::createDirectory(const std::string& path) |
287 | { |
288 | std::string response; |
289 | int status = sendCommand("MKD" , path, response); |
290 | if (!isPositiveCompletion(status)) |
291 | throw FTPException(std::string("Cannot create directory " ) + path, response, status); |
292 | } |
293 | |
294 | |
295 | void FTPClientSession::removeDirectory(const std::string& path) |
296 | { |
297 | std::string response; |
298 | int status = sendCommand("RMD" , path, response); |
299 | if (!isPositiveCompletion(status)) |
300 | throw FTPException(std::string("Cannot remove directory " ) + path, response, status); |
301 | } |
302 | |
303 | |
304 | std::istream& FTPClientSession::beginDownload(const std::string& path) |
305 | { |
306 | if (!isOpen()) |
307 | throw FTPException("Connection is closed." ); |
308 | |
309 | delete _pDataStream; |
310 | _pDataStream = 0; |
311 | _pDataStream = new SocketStream(establishDataConnection("RETR" , path)); |
312 | return *_pDataStream; |
313 | } |
314 | |
315 | |
316 | void FTPClientSession::endDownload() |
317 | { |
318 | endTransfer(); |
319 | } |
320 | |
321 | |
322 | std::ostream& FTPClientSession::beginUpload(const std::string& path) |
323 | { |
324 | if (!isOpen()) |
325 | throw FTPException("Connection is closed." ); |
326 | |
327 | delete _pDataStream; |
328 | _pDataStream = 0; |
329 | _pDataStream = new SocketStream(establishDataConnection("STOR" , path)); |
330 | return *_pDataStream; |
331 | } |
332 | |
333 | |
334 | void FTPClientSession::endUpload() |
335 | { |
336 | endTransfer(); |
337 | } |
338 | |
339 | |
340 | std::istream& FTPClientSession::beginList(const std::string& path, bool extended) |
341 | { |
342 | if (!isOpen()) |
343 | throw FTPException("Connection is closed." ); |
344 | |
345 | delete _pDataStream; |
346 | _pDataStream = 0; |
347 | _pDataStream = new SocketStream(establishDataConnection(extended ? "LIST" : "NLST" , path)); |
348 | return *_pDataStream; |
349 | } |
350 | |
351 | |
352 | void FTPClientSession::endList() |
353 | { |
354 | endTransfer(); |
355 | } |
356 | |
357 | |
358 | void FTPClientSession::abort() |
359 | { |
360 | if (!isOpen()) |
361 | throw FTPException("Connection is closed." ); |
362 | |
363 | _pControlSocket->sendByte(DialogSocket::TELNET_IP); |
364 | _pControlSocket->synch(); |
365 | std::string response; |
366 | int status = sendCommand("ABOR" , response); |
367 | if (status == 426) |
368 | status = _pControlSocket->receiveStatusMessage(response); |
369 | if (status != 226) |
370 | throw FTPException("Cannot abort transfer" , response, status); |
371 | } |
372 | |
373 | |
374 | int FTPClientSession::sendCommand(const std::string& command, std::string& response) |
375 | { |
376 | if (!isOpen()) |
377 | throw FTPException("Connection is closed." ); |
378 | |
379 | _pControlSocket->sendMessage(command); |
380 | return _pControlSocket->receiveStatusMessage(response); |
381 | } |
382 | |
383 | |
384 | int FTPClientSession::sendCommand(const std::string& command, const std::string& arg, std::string& response) |
385 | { |
386 | if (!isOpen()) |
387 | throw FTPException("Connection is closed." ); |
388 | |
389 | _pControlSocket->sendMessage(command, arg); |
390 | return _pControlSocket->receiveStatusMessage(response); |
391 | } |
392 | |
393 | |
394 | std::string FTPClientSession::(const std::string& response) |
395 | { |
396 | std::string path; |
397 | std::string::const_iterator it = response.begin(); |
398 | std::string::const_iterator end = response.end(); |
399 | while (it != end && *it != '"') ++it; |
400 | if (it != end) |
401 | { |
402 | ++it; |
403 | while (it != end) |
404 | { |
405 | if (*it == '"') |
406 | { |
407 | ++it; |
408 | if (it == end || (it != end && *it != '"')) break; |
409 | } |
410 | path += *it++; |
411 | } |
412 | } |
413 | return path; |
414 | } |
415 | |
416 | |
417 | StreamSocket FTPClientSession::establishDataConnection(const std::string& command, const std::string& arg) |
418 | { |
419 | if (_passiveMode) |
420 | return passiveDataConnection(command, arg); |
421 | else |
422 | return activeDataConnection(command, arg); |
423 | } |
424 | |
425 | |
426 | StreamSocket FTPClientSession::activeDataConnection(const std::string& command, const std::string& arg) |
427 | { |
428 | if (!isOpen()) |
429 | throw FTPException("Connection is closed." ); |
430 | |
431 | ServerSocket server(SocketAddress(_pControlSocket->address().host(), 0)); |
432 | sendPortCommand(server.address()); |
433 | std::string response; |
434 | int status = sendCommand(command, arg, response); |
435 | if (!isPositivePreliminary(status)) |
436 | throw FTPException(command + " command failed" , response, status); |
437 | if (server.poll(_timeout, Socket::SELECT_READ)) |
438 | return server.acceptConnection(); |
439 | else |
440 | throw FTPException("The server has not initiated a data connection" ); |
441 | } |
442 | |
443 | |
444 | StreamSocket FTPClientSession::passiveDataConnection(const std::string& command, const std::string& arg) |
445 | { |
446 | SocketAddress sa(sendPassiveCommand()); |
447 | StreamSocket sock(sa); |
448 | std::string response; |
449 | int status = sendCommand(command, arg, response); |
450 | if (!isPositivePreliminary(status)) |
451 | throw FTPException(command + " command failed" , response, status); |
452 | return sock; |
453 | } |
454 | |
455 | |
456 | void FTPClientSession::sendPortCommand(const SocketAddress& addr) |
457 | { |
458 | if (_supports1738) |
459 | { |
460 | if (sendEPRT(addr)) |
461 | return; |
462 | else |
463 | _supports1738 = false; |
464 | } |
465 | sendPORT(addr); |
466 | } |
467 | |
468 | |
469 | SocketAddress FTPClientSession::sendPassiveCommand() |
470 | { |
471 | SocketAddress addr; |
472 | if (_supports1738) |
473 | { |
474 | if (sendEPSV(addr)) |
475 | return addr; |
476 | else |
477 | _supports1738 = false; |
478 | } |
479 | sendPASV(addr); |
480 | return addr; |
481 | } |
482 | |
483 | |
484 | bool FTPClientSession::sendEPRT(const SocketAddress& addr) |
485 | { |
486 | std::string arg("|" ); |
487 | arg += addr.af() == AF_INET ? '1' : '2'; |
488 | arg += '|'; |
489 | arg += addr.host().toString(); |
490 | arg += '|'; |
491 | arg += NumberFormatter::format(addr.port()); |
492 | arg += '|'; |
493 | std::string response; |
494 | int status = sendCommand("EPRT" , arg, response); |
495 | if (isPositiveCompletion(status)) |
496 | return true; |
497 | else if (isPermanentNegative(status)) |
498 | return false; |
499 | else |
500 | throw FTPException("EPRT command failed" , response, status); |
501 | } |
502 | |
503 | |
504 | void FTPClientSession::sendPORT(const SocketAddress& addr) |
505 | { |
506 | std::string arg(addr.host().toString()); |
507 | for (std::string::iterator it = arg.begin(); it != arg.end(); ++it) |
508 | { |
509 | if (*it == '.') *it = ','; |
510 | } |
511 | arg += ','; |
512 | Poco::UInt16 port = addr.port(); |
513 | arg += NumberFormatter::format(port/256); |
514 | arg += ','; |
515 | arg += NumberFormatter::format(port % 256); |
516 | std::string response; |
517 | int status = sendCommand("PORT" , arg, response); |
518 | if (!isPositiveCompletion(status)) |
519 | throw FTPException("PORT command failed" , response, status); |
520 | } |
521 | |
522 | |
523 | bool FTPClientSession::sendEPSV(SocketAddress& addr) |
524 | { |
525 | std::string response; |
526 | int status = sendCommand("EPSV" , response); |
527 | if (isPositiveCompletion(status)) |
528 | { |
529 | parseExtAddress(response, addr); |
530 | return true; |
531 | } |
532 | else if (isPermanentNegative(status)) |
533 | { |
534 | return false; |
535 | } |
536 | else throw FTPException("EPSV command failed" , response, status); |
537 | } |
538 | |
539 | |
540 | void FTPClientSession::sendPASV(SocketAddress& addr) |
541 | { |
542 | std::string response; |
543 | int status = sendCommand("PASV" , response); |
544 | if (!isPositiveCompletion(status)) |
545 | throw FTPException("PASV command failed" , response, status); |
546 | parseAddress(response, addr); |
547 | } |
548 | |
549 | |
550 | void FTPClientSession::parseAddress(const std::string& str, SocketAddress& addr) |
551 | { |
552 | std::string::const_iterator it = str.begin(); |
553 | std::string::const_iterator end = str.end(); |
554 | while (it != end && *it != '(') ++it; |
555 | if (it != end) ++it; |
556 | std::string host; |
557 | while (it != end && Poco::Ascii::isDigit(*it)) host += *it++; |
558 | if (it != end && *it == ',') { host += '.'; ++it; } |
559 | while (it != end && Poco::Ascii::isDigit(*it)) host += *it++; |
560 | if (it != end && *it == ',') { host += '.'; ++it; } |
561 | while (it != end && Poco::Ascii::isDigit(*it)) host += *it++; |
562 | if (it != end && *it == ',') { host += '.'; ++it; } |
563 | while (it != end && Poco::Ascii::isDigit(*it)) host += *it++; |
564 | if (it != end && *it == ',') ++it; |
565 | Poco::UInt16 portHi = 0; |
566 | while (it != end && Poco::Ascii::isDigit(*it)) { portHi *= 10; portHi += *it++ - '0'; } |
567 | if (it != end && *it == ',') ++it; |
568 | Poco::UInt16 portLo = 0; |
569 | while (it != end && Poco::Ascii::isDigit(*it)) { portLo *= 10; portLo += *it++ - '0'; } |
570 | addr = SocketAddress(host, portHi*256 + portLo); |
571 | } |
572 | |
573 | |
574 | void FTPClientSession::parseExtAddress(const std::string& str, SocketAddress& addr) |
575 | { |
576 | std::string::const_iterator it = str.begin(); |
577 | std::string::const_iterator end = str.end(); |
578 | while (it != end && *it != '(') ++it; |
579 | if (it != end) ++it; |
580 | char delim = '|'; |
581 | if (it != end) delim = *it++; |
582 | if (it != end && *it == delim) ++it; |
583 | if (it != end && *it == delim) ++it; |
584 | Poco::UInt16 port = 0; |
585 | while (it != end && Poco::Ascii::isDigit(*it)) { port *= 10; port += *it++ - '0'; } |
586 | addr = SocketAddress(_pControlSocket->peerAddress().host(), port); |
587 | } |
588 | |
589 | |
590 | void FTPClientSession::endTransfer() |
591 | { |
592 | if (_pDataStream) |
593 | { |
594 | delete _pDataStream; |
595 | _pDataStream = 0; |
596 | std::string response; |
597 | int status = _pControlSocket->receiveStatusMessage(response); |
598 | if (!isPositiveCompletion(status)) |
599 | throw FTPException("Data transfer failed" , response, status); |
600 | } |
601 | } |
602 | |
603 | } } // namespace Poco::Net |
604 | |