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