1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt3Support module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qplatformdefs.h"
43 #include "q3ftp.h"
44 
45 #ifndef QT_NO_NETWORKPROTOCOL_FTP
46 
47 #include "q3socket.h"
48 #include "q3socketdevice.h"
49 #include "qurlinfo.h"
50 #include "q3urloperator.h"
51 #include "qstringlist.h"
52 #include "qregexp.h"
53 #include "qtimer.h"
54 #include "qfileinfo.h"
55 #include "q3ptrdict.h"
56 #include "q3cstring.h"
57 #include "qcoreapplication.h"
58 #include "qftp.h"
59 
60 #ifndef QT_NO_TEXTCODEC
61 #include "qtextcodec.h"
62 #endif
63 
64 QT_BEGIN_NAMESPACE
65 
66 //#define Q3FTPPI_DEBUG
67 //#define Q3FTPDTP_DEBUG
68 
69 class Q3FtpPI;
70 
71 class Q3FtpDTP : public QObject
72 {
73     Q_OBJECT
74 
75 public:
76     enum ConnectState {
77 	CsHostFound,
78 	CsConnected,
79 	CsClosed,
80 	CsHostNotFound,
81 	CsConnectionRefused
82     };
83 
84     Q3FtpDTP( Q3FtpPI *p, QObject *parent=0, const char *name=0 );
85 
86     void setData( QByteArray * );
87     void setDevice( QIODevice * );
88     void writeData();
89 
setBytesTotal(int bytes)90     void setBytesTotal( int bytes )
91     {
92 	bytesTotal = bytes;
93 	bytesDone = 0;
94 	emit dataTransferProgress( bytesDone, bytesTotal );
95     }
96 
97     bool hasError() const;
98     QString errorMessage() const;
99     void clearError();
100 
connectToHost(const QString & host,Q_UINT16 port)101     void connectToHost( const QString & host, Q_UINT16 port )
102     { socket.connectToHost( host, port ); }
103 
socketState() const104     Q3Socket::State socketState() const
105     { return socket.state(); }
106 
bytesAvailable() const107     Q_ULONG bytesAvailable() const
108     { return socket.bytesAvailable(); }
109 
readBlock(char * data,Q_ULONG maxlen)110     Q_LONG readBlock( char *data, Q_ULONG maxlen )
111     {
112 	Q_LONG read = socket.readBlock( data, maxlen );
113 	bytesDone += read;
114 	return read;
115     }
116 
readAll()117     QByteArray readAll()
118     {
119 	QByteArray tmp = socket.readAll();
120 	bytesDone += tmp.size();
121 	return tmp;
122     }
123 
124     void abortConnection();
125 
126     static bool parseDir( const QString &buffer, const QString &userName, QUrlInfo *info );
127 
128 signals:
129     void listInfo( const QUrlInfo& );
130     void readyRead();
131     void dataTransferProgress( int, int );
132 
133     void connectState( int );
134 
135 private slots:
136     void socketConnected();
137     void socketReadyRead();
138     void socketError( int );
139     void socketConnectionClosed();
140     void socketBytesWritten( int );
141 
142 private:
clearData()143     void clearData()
144     {
145 	is_ba = false;
146 	data.dev = 0;
147     }
148 
149     Q3Socket socket;
150     Q3FtpPI *pi;
151     QString err;
152     int bytesDone;
153     int bytesTotal;
154     bool callWriteData;
155 
156     // If is_ba is true, ba is used; ba is never 0.
157     // Otherwise dev is used; dev can be 0 or not.
158     union {
159 	QByteArray *ba;
160 	QIODevice *dev;
161     } data;
162     bool is_ba;
163 };
164 
165 class Q3FtpPI : public QObject
166 {
167     Q_OBJECT
168 
169 public:
170     Q3FtpPI( QObject *parent = 0 );
171 
172     void connectToHost( const QString &host, Q_UINT16 port );
173 
174     bool sendCommands( const QStringList &cmds );
sendCommand(const QString & cmd)175     bool sendCommand( const QString &cmd )
176     { return sendCommands( QStringList( cmd ) ); }
177 
178     void clearPendingCommands();
179     void abort();
180 
currentCommand() const181     QString currentCommand() const
182     { return currentCmd; }
183 
184     bool rawCommand;
185 
186     Q3FtpDTP dtp; // the PI has a DTP which is not the design of RFC 959, but it
187 		 // makes the design simpler this way
188 signals:
189     void connectState( int );
190     void finished( const QString& );
191     void error( int, const QString& );
192     void rawFtpReply( int, const QString& );
193 
194 private slots:
195     void hostFound();
196     void connected();
197     void connectionClosed();
198     void delayedCloseFinished();
199     void readyRead();
200     void error( int );
201 
202     void dtpConnectState( int );
203 
204 private:
205     // the states are modelled after the generalized state diagram of RFC 959,
206     // page 58
207     enum State {
208 	Begin,
209 	Idle,
210 	Waiting,
211 	Success,
212 	Failure
213     };
214 
215     enum AbortState {
216 	None,
217 	AbortStarted,
218 	WaitForAbortToFinish
219     };
220 
221     bool processReply();
222     bool startNextCmd();
223 
224     Q3Socket commandSocket;
225     QString replyText;
226     signed char replyCode[3];
227     State state;
228     AbortState abortState;
229     QStringList pendingCommands;
230     QString currentCmd;
231 
232     bool waitForDtpToConnect;
233     bool waitForDtpToClose;
234 };
235 
236 /**********************************************************************
237  *
238  * Q3FtpCommand implemenatation
239  *
240  *********************************************************************/
241 class Q3FtpCommand
242 {
243 public:
244     Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw );
245     Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, const QByteArray &ba );
246     Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, QIODevice *dev );
247     ~Q3FtpCommand();
248 
249     int id;
250     Q3Ftp::Command command;
251     QStringList rawCmds;
252 
253     // If is_ba is true, ba is used; ba is never 0.
254     // Otherwise dev is used; dev can be 0 or not.
255     union {
256 	QByteArray *ba;
257 	QIODevice *dev;
258     } data;
259     bool is_ba;
260 
261     static int idCounter;
262 };
263 
264 int Q3FtpCommand::idCounter = 0;
265 
Q3FtpCommand(Q3Ftp::Command cmd,QStringList raw)266 Q3FtpCommand::Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw )
267     : command(cmd), rawCmds(raw), is_ba(false)
268 {
269     id = ++idCounter;
270     data.dev = 0;
271 }
272 
Q3FtpCommand(Q3Ftp::Command cmd,QStringList raw,const QByteArray & ba)273 Q3FtpCommand::Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, const QByteArray &ba )
274     : command(cmd), rawCmds(raw), is_ba(true)
275 {
276     id = ++idCounter;
277     data.ba = new QByteArray( ba );
278 }
279 
Q3FtpCommand(Q3Ftp::Command cmd,QStringList raw,QIODevice * dev)280 Q3FtpCommand::Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, QIODevice *dev )
281     : command(cmd), rawCmds(raw), is_ba(false)
282 {
283     id = ++idCounter;
284     data.dev = dev;
285 }
286 
~Q3FtpCommand()287 Q3FtpCommand::~Q3FtpCommand()
288 {
289     if ( is_ba )
290 	delete data.ba;
291 }
292 
293 /**********************************************************************
294  *
295  * Q3FtpDTP implemenatation
296  *
297  *********************************************************************/
Q3FtpDTP(Q3FtpPI * p,QObject * parent,const char * name)298 Q3FtpDTP::Q3FtpDTP( Q3FtpPI *p, QObject *parent, const char *name ) :
299     QObject( parent, name ),
300     socket( 0, "Q3FtpDTP_socket" ),
301     pi( p ),
302     callWriteData( false )
303 {
304     clearData();
305 
306     connect( &socket, SIGNAL(connected()),
307 	     SLOT(socketConnected()) );
308     connect( &socket, SIGNAL(readyRead()),
309 	     SLOT(socketReadyRead()) );
310     connect( &socket, SIGNAL(error(int)),
311 	     SLOT(socketError(int)) );
312     connect( &socket, SIGNAL(connectionClosed()),
313 	     SLOT(socketConnectionClosed()) );
314     connect( &socket, SIGNAL(bytesWritten(int)),
315 	     SLOT(socketBytesWritten(int)) );
316 }
317 
setData(QByteArray * ba)318 void Q3FtpDTP::setData( QByteArray *ba )
319 {
320     is_ba = true;
321     data.ba = ba;
322 }
323 
setDevice(QIODevice * dev)324 void Q3FtpDTP::setDevice( QIODevice *dev )
325 {
326     is_ba = false;
327     data.dev = dev;
328 }
329 
writeData()330 void Q3FtpDTP::writeData()
331 {
332     if ( is_ba ) {
333 #if defined(Q3FTPDTP_DEBUG)
334 	qDebug( "Q3FtpDTP::writeData: write %d bytes", data.ba->size() );
335 #endif
336 	if ( data.ba->size() == 0 )
337 	    emit dataTransferProgress( 0, bytesTotal );
338 	else
339 	    socket.writeBlock( data.ba->data(), data.ba->size() );
340 	socket.close();
341 	clearData();
342     } else if ( data.dev ) {
343 	callWriteData = false;
344 	const int blockSize = 16*1024;
345 	char buf[blockSize];
346 	while ( !data.dev->atEnd() && socket.bytesToWrite()==0 ) {
347 	    Q_LONG read = data.dev->readBlock( buf, blockSize );
348 #if defined(Q3FTPDTP_DEBUG)
349 	    qDebug( "Q3FtpDTP::writeData: writeBlock() of size %d bytes", (int)read );
350 #endif
351 	    socket.writeBlock( buf, read );
352 	    if ( !data.dev )
353 		return; // this can happen when a command is aborted
354 	}
355 	if ( data.dev->atEnd() ) {
356 	    if ( bytesDone==0 && socket.bytesToWrite()==0 )
357 		emit dataTransferProgress( 0, bytesTotal );
358 	    socket.close();
359 	    clearData();
360 	} else {
361 	    callWriteData = true;
362 	}
363     }
364 }
365 
hasError() const366 inline bool Q3FtpDTP::hasError() const
367 {
368     return !err.isNull();
369 }
370 
errorMessage() const371 inline QString Q3FtpDTP::errorMessage() const
372 {
373     return err;
374 }
375 
clearError()376 inline void Q3FtpDTP::clearError()
377 {
378     err.clear();
379 }
380 
abortConnection()381 void Q3FtpDTP::abortConnection()
382 {
383 #if defined(Q3FTPDTP_DEBUG)
384     qDebug( "Q3FtpDTP::abortConnection" );
385 #endif
386     callWriteData = false;
387     clearData();
388 
389     socket.clearPendingData();
390     socket.close();
391 }
392 
parseDir(const QString & buffer,const QString & userName,QUrlInfo * info)393 bool Q3FtpDTP::parseDir( const QString &buffer, const QString &userName, QUrlInfo *info )
394 {
395     QStringList lst = QStringList::split( QLatin1String(" "), buffer );
396 
397     if ( lst.count() < 9 )
398 	return false;
399 
400     QString tmp;
401 
402     // permissions
403     tmp = lst[ 0 ];
404 
405     if ( tmp[ 0 ] == QChar( QLatin1Char('d') ) ) {
406 	info->setDir( true );
407 	info->setFile( false );
408 	info->setSymLink( false );
409     } else if ( tmp[ 0 ] == QChar( QLatin1Char('-') ) ) {
410 	info->setDir( false );
411 	info->setFile( true );
412 	info->setSymLink( false );
413     } else if ( tmp[ 0 ] == QChar( QLatin1Char('l') ) ) {
414 	info->setDir( true ); // #### todo
415 	info->setFile( false );
416 	info->setSymLink( true );
417     } else {
418 	return false;
419     }
420 
421     static int user = 0;
422     static int group = 1;
423     static int other = 2;
424     static int readable = 0;
425     static int writable = 1;
426     static int executable = 2;
427 
428     bool perms[ 3 ][ 3 ];
429     perms[0][0] = (tmp[ 1 ] == QLatin1Char('r'));
430     perms[0][1] = (tmp[ 2 ] == QLatin1Char('w'));
431     perms[0][2] = (tmp[ 3 ] == QLatin1Char('x'));
432     perms[1][0] = (tmp[ 4 ] == QLatin1Char('r'));
433     perms[1][1] = (tmp[ 5 ] == QLatin1Char('w'));
434     perms[1][2] = (tmp[ 6 ] == QLatin1Char('x'));
435     perms[2][0] = (tmp[ 7 ] == QLatin1Char('r'));
436     perms[2][1] = (tmp[ 8 ] == QLatin1Char('w'));
437     perms[2][2] = (tmp[ 9 ] == QLatin1Char('x'));
438 
439     // owner
440     tmp = lst[ 2 ];
441     info->setOwner( tmp );
442 
443     // group
444     tmp = lst[ 3 ];
445     info->setGroup( tmp );
446 
447     // ### not correct
448     info->setWritable( ( userName == info->owner() && perms[ user ][ writable ] ) ||
449 	perms[ other ][ writable ] );
450     info->setReadable( ( userName == info->owner() && perms[ user ][ readable ] ) ||
451 	perms[ other ][ readable ] );
452 
453     int p = 0;
454     if ( perms[ user ][ readable ] )
455 	p |= QUrlInfo::ReadOwner;
456     if ( perms[ user ][ writable ] )
457 	p |= QUrlInfo::WriteOwner;
458     if ( perms[ user ][ executable ] )
459 	p |= QUrlInfo::ExeOwner;
460     if ( perms[ group ][ readable ] )
461 	p |= QUrlInfo::ReadGroup;
462     if ( perms[ group ][ writable ] )
463 	p |= QUrlInfo::WriteGroup;
464     if ( perms[ group ][ executable ] )
465 	p |= QUrlInfo::ExeGroup;
466     if ( perms[ other ][ readable ] )
467 	p |= QUrlInfo::ReadOther;
468     if ( perms[ other ][ writable ] )
469 	p |= QUrlInfo::WriteOther;
470     if ( perms[ other ][ executable ] )
471 	p |= QUrlInfo::ExeOther;
472     info->setPermissions( p );
473 
474     // size
475     tmp = lst[ 4 ];
476     info->setSize( tmp.toInt() );
477 
478     // date and time
479     QTime time;
480     QString dateStr;
481     dateStr += QLatin1String("Sun ");
482     lst[ 5 ][ 0 ] = lst[ 5 ][ 0 ].upper();
483     dateStr += lst[ 5 ];
484     dateStr += QLatin1Char(' ');
485     dateStr += lst[ 6 ];
486     dateStr += QLatin1Char(' ');
487 
488     if ( lst[ 7 ].contains( QLatin1Char(':') ) ) {
489 	time = QTime( lst[ 7 ].left( 2 ).toInt(), lst[ 7 ].right( 2 ).toInt() );
490 	dateStr += QString::number( QDate::currentDate().year() );
491     } else {
492 	dateStr += lst[ 7 ];
493     }
494 
495     QDate date = QDate::fromString( dateStr );
496     info->setLastModified( QDateTime( date, time ) );
497 
498     if ( lst[ 7 ].contains( QLatin1Char(':') ) ) {
499 	const int futureTolerance = 600;
500 	if( info->lastModified().secsTo( QDateTime::currentDateTime() ) < -futureTolerance ) {
501 	    QDateTime dt = info->lastModified();
502 	    QDate d = dt.date();
503 	    d.setYMD(d.year()-1, d.month(), d.day());
504 	    dt.setDate(d);
505 	    info->setLastModified(dt);
506 	}
507     }
508 
509     // name
510     if ( info->isSymLink() )
511 	info->setName( lst[ 8 ].stripWhiteSpace() );
512     else {
513 	QString n;
514 	for ( uint i = 8; i < (uint) lst.count(); ++i )
515 	    n += lst[ i ] + QLatin1Char(' ');
516 	n = n.stripWhiteSpace();
517 	info->setName( n );
518     }
519     return true;
520 }
521 
socketConnected()522 void Q3FtpDTP::socketConnected()
523 {
524 #if !defined (Q_WS_QWS)
525     // Use a large send buffer to reduce the number
526     // of writeBlocks when download and uploading files.
527     // The actual size used here (128k) is default on most
528     // Unixes.
529     socket.socketDevice()->setSendBufferSize(128 * 1024);
530     socket.socketDevice()->setReceiveBufferSize(128 * 1024);
531 #endif
532 
533     bytesDone = 0;
534 #if defined(Q3FTPDTP_DEBUG)
535     qDebug( "Q3FtpDTP::connectState( CsConnected )" );
536 #endif
537     emit connectState( Q3FtpDTP::CsConnected );
538 }
539 
socketReadyRead()540 void Q3FtpDTP::socketReadyRead()
541 {
542     if ( pi->currentCommand().isEmpty() ) {
543 	socket.close();
544 #if defined(Q3FTPDTP_DEBUG)
545 	qDebug( "Q3FtpDTP::connectState( CsClosed )" );
546 #endif
547 	emit connectState( Q3FtpDTP::CsClosed );
548 	return;
549     }
550 
551     if ( pi->currentCommand().startsWith(QLatin1String("LIST")) ) {
552 	while ( socket.canReadLine() ) {
553 	    QUrlInfo i;
554 	    QString line = QLatin1String(socket.readLine());
555 #if defined(Q3FTPDTP_DEBUG)
556 	    qDebug( "Q3FtpDTP read (list): '%s'", line.latin1() );
557 #endif
558 	    if ( parseDir( line, QLatin1String(""), &i ) ) {
559 		emit listInfo( i );
560 	    } else {
561 		// some FTP servers don't return a 550 if the file or directory
562 		// does not exist, but rather write a text to the data socket
563 		// -- try to catch these cases
564 		if ( line.endsWith( QLatin1String("No such file or directory\r\n") ) )
565 		    err = line;
566 	    }
567 	}
568     } else {
569 	if ( !is_ba && data.dev ) {
570 	    QByteArray ba( socket.bytesAvailable() );
571 	    Q_LONG bytesRead = socket.readBlock( ba.data(), ba.size() );
572 	    if ( bytesRead < 0 ) {
573 		// ### error handling
574 		return;
575 	    }
576 	    ba.resize( bytesRead );
577 	    bytesDone += bytesRead;
578 #if defined(Q3FTPDTP_DEBUG)
579 	    qDebug( "Q3FtpDTP read: %d bytes (total %d bytes)", (int)bytesRead, bytesDone );
580 #endif
581 	    emit dataTransferProgress( bytesDone, bytesTotal );
582             if (data.dev)       // make sure it wasn't deleted in the slot
583                 data.dev->writeBlock( ba );
584 	} else {
585 #if defined(Q3FTPDTP_DEBUG)
586 	    qDebug( "Q3FtpDTP readyRead: %d bytes available (total %d bytes read)", (int)bytesAvailable(), bytesDone );
587 #endif
588 	    emit dataTransferProgress( bytesDone+socket.bytesAvailable(), bytesTotal );
589 	    emit readyRead();
590 	}
591     }
592 }
593 
socketError(int e)594 void Q3FtpDTP::socketError( int e )
595 {
596     if ( e == Q3Socket::ErrHostNotFound ) {
597 #if defined(Q3FTPDTP_DEBUG)
598 	qDebug( "Q3FtpDTP::connectState( CsHostNotFound )" );
599 #endif
600 	emit connectState( Q3FtpDTP::CsHostNotFound );
601     } else if ( e == Q3Socket::ErrConnectionRefused ) {
602 #if defined(Q3FTPDTP_DEBUG)
603 	qDebug( "Q3FtpDTP::connectState( CsConnectionRefused )" );
604 #endif
605 	emit connectState( Q3FtpDTP::CsConnectionRefused );
606     }
607 }
608 
socketConnectionClosed()609 void Q3FtpDTP::socketConnectionClosed()
610 {
611     if ( !is_ba && data.dev ) {
612 	clearData();
613     }
614 #if defined(Q3FTPDTP_DEBUG)
615     qDebug( "Q3FtpDTP::connectState( CsClosed )" );
616 #endif
617     emit connectState( Q3FtpDTP::CsClosed );
618 }
619 
socketBytesWritten(int bytes)620 void Q3FtpDTP::socketBytesWritten( int bytes )
621 {
622     bytesDone += bytes;
623 #if defined(Q3FTPDTP_DEBUG)
624     qDebug( "Q3FtpDTP::bytesWritten( %d )", bytesDone );
625 #endif
626     emit dataTransferProgress( bytesDone, bytesTotal );
627     if ( callWriteData )
628 	writeData();
629 }
630 
631 /**********************************************************************
632  *
633  * Q3FtpPI implemenatation
634  *
635  *********************************************************************/
Q3FtpPI(QObject * parent)636 Q3FtpPI::Q3FtpPI( QObject *parent ) :
637     QObject( parent ),
638     rawCommand(false),
639     dtp( this ),
640     commandSocket( 0, "Q3FtpPI_socket" ),
641     state( Begin ), abortState( None ),
642     currentCmd( QString() ),
643     waitForDtpToConnect( false ),
644     waitForDtpToClose( false )
645 {
646     connect( &commandSocket, SIGNAL(hostFound()),
647 	    SLOT(hostFound()) );
648     connect( &commandSocket, SIGNAL(connected()),
649 	    SLOT(connected()) );
650     connect( &commandSocket, SIGNAL(connectionClosed()),
651 	    SLOT(connectionClosed()) );
652     connect( &commandSocket, SIGNAL(delayedCloseFinished()),
653 	    SLOT(delayedCloseFinished()) );
654     connect( &commandSocket, SIGNAL(readyRead()),
655 	    SLOT(readyRead()) );
656     connect( &commandSocket, SIGNAL(error(int)),
657 	    SLOT(error(int)) );
658 
659     connect( &dtp, SIGNAL(connectState(int)),
660 	     SLOT(dtpConnectState(int)) );
661 }
662 
connectToHost(const QString & host,Q_UINT16 port)663 void Q3FtpPI::connectToHost( const QString &host, Q_UINT16 port )
664 {
665     emit connectState( Q3Ftp::HostLookup );
666     commandSocket.connectToHost( host, port );
667 }
668 
669 /*
670   Sends the sequence of commands \a cmds to the FTP server. When the commands
671   are all done the finished() signal is emitted. When an error occurs, the
672   error() signal is emitted.
673 
674   If there are pending commands in the queue this functions returns false and
675   the \a cmds are not added to the queue; otherwise it returns true.
676 */
sendCommands(const QStringList & cmds)677 bool Q3FtpPI::sendCommands( const QStringList &cmds )
678 {
679     if ( !pendingCommands.isEmpty() )
680 	return false;
681 
682     if ( commandSocket.state()!=Q3Socket::Connected || state!=Idle ) {
683 	emit error( Q3Ftp::NotConnected, QFtp::tr( "Not connected" ) );
684 	return true; // there are no pending commands
685     }
686 
687     pendingCommands = cmds;
688     startNextCmd();
689     return true;
690 }
691 
clearPendingCommands()692 void Q3FtpPI::clearPendingCommands()
693 {
694     pendingCommands.clear();
695     dtp.abortConnection();
696     currentCmd.clear();
697     state = Idle;
698 }
699 
abort()700 void Q3FtpPI::abort()
701 {
702     pendingCommands.clear();
703 
704     if ( abortState != None )
705 	// ABOR already sent
706 	return;
707 
708     abortState = AbortStarted;
709 #if defined(Q3FTPPI_DEBUG)
710     qDebug( "Q3FtpPI send: ABOR" );
711 #endif
712     commandSocket.writeBlock( "ABOR\r\n", 6 );
713 
714     if ( currentCmd.startsWith(QLatin1String("STOR ")) )
715 	dtp.abortConnection();
716 }
717 
hostFound()718 void Q3FtpPI::hostFound()
719 {
720     emit connectState( Q3Ftp::Connecting );
721 }
722 
connected()723 void Q3FtpPI::connected()
724 {
725     state = Begin;
726 #if defined(Q3FTPPI_DEBUG)
727 //    qDebug( "Q3FtpPI state: %d [connected()]", state );
728 #endif
729     emit connectState( Q3Ftp::Connected );
730 }
731 
connectionClosed()732 void Q3FtpPI::connectionClosed()
733 {
734     commandSocket.close();
735     emit connectState( Q3Ftp::Unconnected );
736 }
737 
delayedCloseFinished()738 void Q3FtpPI::delayedCloseFinished()
739 {
740     emit connectState( Q3Ftp::Unconnected );
741 }
742 
error(int e)743 void Q3FtpPI::error( int e )
744 {
745     if ( e == Q3Socket::ErrHostNotFound ) {
746 	emit connectState( Q3Ftp::Unconnected );
747 	emit error( Q3Ftp::HostNotFound,
748 		    QFtp::tr( "Host %1 not found" ).arg( commandSocket.peerName() ) );
749     } else if ( e == Q3Socket::ErrConnectionRefused ) {
750 	emit connectState( Q3Ftp::Unconnected );
751 	emit error( Q3Ftp::ConnectionRefused,
752 		    QFtp::tr( "Connection refused to host %1" ).arg( commandSocket.peerName() ) );
753     }
754 }
755 
readyRead()756 void Q3FtpPI::readyRead()
757 {
758     if ( waitForDtpToClose )
759 	return;
760 
761     while ( commandSocket.canReadLine() ) {
762 	// read line with respect to line continuation
763 	QString line = QLatin1String(commandSocket.readLine());
764 	if ( replyText.isEmpty() ) {
765 	    if ( line.length() < 3 ) {
766 		// ### protocol error
767 		return;
768 	    }
769 	    const int lowerLimit[3] = {1,0,0};
770 	    const int upperLimit[3] = {5,5,9};
771 	    for ( int i=0; i<3; i++ ) {
772 		replyCode[i] = line[i].digitValue();
773 		if ( replyCode[i]<lowerLimit[i] || replyCode[i]>upperLimit[i] ) {
774 		    // ### protocol error
775 		    return;
776 		}
777 	    }
778 	}
779 	QString endOfMultiLine;
780 	endOfMultiLine[0] = '0' + replyCode[0];
781 	endOfMultiLine[1] = '0' + replyCode[1];
782 	endOfMultiLine[2] = '0' + replyCode[2];
783 	endOfMultiLine[3] = ' ';
784 	QString lineCont( endOfMultiLine );
785 	lineCont[3] = '-';
786 	QString lineLeft4 = line.left(4);
787 
788 	while ( lineLeft4 != endOfMultiLine ) {
789 	    if ( lineLeft4 == lineCont )
790 		replyText += line.mid( 4 ); // strip 'xyz-'
791 	    else
792 		replyText += line;
793 	    if ( !commandSocket.canReadLine() )
794 		return;
795 	    line = QLatin1String(commandSocket.readLine());
796 	    lineLeft4 = line.left(4);
797 	}
798 	replyText += line.mid( 4 ); // strip reply code 'xyz '
799 	if ( replyText.endsWith(QLatin1String("\r\n")) )
800 	    replyText.truncate( replyText.length()-2 );
801 
802 	if ( processReply() )
803 	    replyText = QLatin1String("");
804     }
805 }
806 
807 /*
808   Process a reply from the FTP server.
809 
810   Returns true if the reply was processed or false if the reply has to be
811   processed at a later point.
812 */
processReply()813 bool Q3FtpPI::processReply()
814 {
815 #if defined(Q3FTPPI_DEBUG)
816 //    qDebug( "Q3FtpPI state: %d [processReply() begin]", state );
817     if ( replyText.length() < 400 )
818 	qDebug( "Q3FtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.latin1() );
819     else
820 	qDebug( "Q3FtpPI recv: %d (text skipped)", 100*replyCode[0]+10*replyCode[1]+replyCode[2] );
821 #endif
822 
823     // process 226 replies ("Closing Data Connection") only when the data
824     // connection is really closed to avoid short reads of the DTP
825     if ( 100*replyCode[0]+10*replyCode[1]+replyCode[2] == 226 ) {
826 	if ( dtp.socketState() != Q3Socket::Idle ) {
827 	    waitForDtpToClose = true;
828 	    return false;
829 	}
830     }
831 
832     switch ( abortState ) {
833 	case AbortStarted:
834 	    abortState = WaitForAbortToFinish;
835 	    break;
836 	case WaitForAbortToFinish:
837 	    abortState = None;
838 	    return true;
839 	default:
840 	    break;
841     }
842 
843     // get new state
844     static const State table[5] = {
845 	/* 1yz   2yz      3yz   4yz      5yz */
846 	Waiting, Success, Idle, Failure, Failure
847     };
848     switch ( state ) {
849 	case Begin:
850 	    if ( replyCode[0] == 1 ) {
851 		return true;
852 	    } else if ( replyCode[0] == 2 ) {
853 		state = Idle;
854 		emit finished( QFtp::tr( "Connected to host %1" ).arg( commandSocket.peerName() ) );
855 		break;
856 	    }
857 	    // ### error handling
858 	    return true;
859 	case Waiting:
860 	    if ( replyCode[0]<0 || replyCode[0]>5 )
861 		state = Failure;
862 	    else
863 #if defined(Q_OS_IRIX) && defined(Q_CC_GNU)
864             {
865                 // work around a crash on 64 bit gcc IRIX
866                 State *t = (State *) table;
867                 state = t[replyCode[0] - 1];
868             }
869 #else
870             state = table[replyCode[0] - 1];
871 #endif
872 	    break;
873 	default:
874 	    // ### spontaneous message
875 	    return true;
876     }
877 #if defined(Q3FTPPI_DEBUG)
878 //    qDebug( "Q3FtpPI state: %d [processReply() intermediate]", state );
879 #endif
880 
881     // special actions on certain replies
882     int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2];
883     emit rawFtpReply( replyCodeInt, replyText );
884     if ( rawCommand ) {
885 	rawCommand = false;
886     } else if ( replyCodeInt == 227 ) {
887 	// 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)
888         // rfc959 does not define this response precisely, and gives
889         // both examples where the parenthesis are used, and where
890         // they are missing. We need to scan for the address and host
891         // info.
892 	QRegExp addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"));
893 	if (addrPortPattern.search(replyText) == -1) {
894 #if defined(Q3FTPPI_DEBUG)
895 	    qDebug( "Q3Ftp: bad 227 response -- address and port information missing" );
896 #endif
897 	    // ### error handling
898 	} else {
899 	    QStringList lst = addrPortPattern.capturedTexts();
900 	    QString host = lst[1] + QLatin1Char('.') + lst[2] + QLatin1Char('.') + lst[3] + QLatin1Char('.') + lst[4];
901 	    Q_UINT16 port = ( lst[5].toUInt() << 8 ) + lst[6].toUInt();
902 	    waitForDtpToConnect = true;
903 	    dtp.connectToHost( host, port );
904 	}
905     } else if ( replyCodeInt == 230 ) {
906 	if ( currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count()>0 &&
907 		pendingCommands.first().startsWith(QLatin1String("PASS ")) ) {
908 	    // no need to send the PASS -- we are already logged in
909 	    pendingCommands.pop_front();
910 	}
911 	// 230 User logged in, proceed.
912 	emit connectState( Q3Ftp::LoggedIn );
913     } else if ( replyCodeInt == 213 ) {
914 	// 213 File status.
915 	if ( currentCmd.startsWith(QLatin1String("SIZE ")) )
916 	    dtp.setBytesTotal( replyText.simplifyWhiteSpace().toInt() );
917     } else if ( replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR ")) ) {
918 	dtp.writeData();
919     }
920 
921     // react on new state
922     switch ( state ) {
923 	case Begin:
924 	    // ### should never happen
925 	    break;
926 	case Success:
927 	    // ### success handling
928 	    state = Idle;
929 	    // no break!
930 	case Idle:
931 	    if ( dtp.hasError() ) {
932 		emit error( Q3Ftp::UnknownError, dtp.errorMessage() );
933 		dtp.clearError();
934 	    }
935 	    startNextCmd();
936 	    break;
937 	case Waiting:
938 	    // ### do nothing
939 	    break;
940 	case Failure:
941 	    emit error( Q3Ftp::UnknownError, replyText );
942 	    state = Idle;
943 	    startNextCmd();
944 	    break;
945     }
946 #if defined(Q3FTPPI_DEBUG)
947 //    qDebug( "Q3FtpPI state: %d [processReply() end]", state );
948 #endif
949     return true;
950 }
951 
952 #ifndef QT_NO_TEXTCODEC
953 Q_COMPAT_EXPORT QTextCodec *qt_ftp_filename_codec = 0;
954 #endif
955 
956 /*
957   Starts next pending command. Returns false if there are no pending commands,
958   otherwise it returns true.
959 */
startNextCmd()960 bool Q3FtpPI::startNextCmd()
961 {
962     if ( waitForDtpToConnect )
963 	// don't process any new commands until we are connected
964 	return true;
965 
966 #if defined(Q3FTPPI_DEBUG)
967     if ( state != Idle )
968 	qDebug( "Q3FtpPI startNextCmd: Internal error! Q3FtpPI called in non-Idle state %d", state );
969 #endif
970     if ( pendingCommands.isEmpty() ) {
971 	currentCmd.clear();
972 	emit finished( replyText );
973 	return false;
974     }
975     currentCmd = pendingCommands.first();
976     pendingCommands.pop_front();
977 #if defined(Q3FTPPI_DEBUG)
978     qDebug( "Q3FtpPI send: %s", currentCmd.left( currentCmd.length()-2 ).latin1() );
979 #endif
980     state = Waiting;
981 #ifndef QT_NO_TEXTCODEC
982     if ( qt_ftp_filename_codec ) {
983 	int len;
984 	Q3CString enc = qt_ftp_filename_codec->fromUnicode(currentCmd,len);
985 	commandSocket.writeBlock( enc.data(), len );
986     } else
987 #endif
988     {
989 	commandSocket.writeBlock( currentCmd.latin1(), currentCmd.length() );
990     }
991     return true;
992 }
993 
dtpConnectState(int s)994 void Q3FtpPI::dtpConnectState( int s )
995 {
996     switch ( s ) {
997 	case Q3FtpDTP::CsClosed:
998 	    if ( waitForDtpToClose ) {
999 		// there is an unprocessed reply
1000 		if ( processReply() )
1001 		    replyText = QLatin1String("");
1002 		else
1003 		    return;
1004 	    }
1005 	    waitForDtpToClose = false;
1006 	    readyRead();
1007 	    return;
1008 	case Q3FtpDTP::CsConnected:
1009 	    waitForDtpToConnect = false;
1010 	    startNextCmd();
1011 	    return;
1012 	case Q3FtpDTP::CsHostNotFound:
1013 	case Q3FtpDTP::CsConnectionRefused:
1014 	    emit error( Q3Ftp::ConnectionRefused,
1015 			QFtp::tr( "Connection refused for data connection" ) );
1016 	    startNextCmd();
1017 	    return;
1018 	default:
1019 	    return;
1020     }
1021 }
1022 
1023 /**********************************************************************
1024  *
1025  * Q3FtpPrivate
1026  *
1027  *********************************************************************/
1028 class Q3FtpPrivate
1029 {
1030 public:
Q3FtpPrivate()1031     Q3FtpPrivate() :
1032 	close_waitForStateChange(false),
1033 	state( Q3Ftp::Unconnected ),
1034 	error( Q3Ftp::NoError ),
1035 	npWaitForLoginDone( false )
1036     { pending.setAutoDelete( true ); }
1037 
1038     Q3FtpPI pi;
1039     Q3PtrList<Q3FtpCommand> pending;
1040     bool close_waitForStateChange;
1041     Q3Ftp::State state;
1042     Q3Ftp::Error error;
1043     QString errorString;
1044 
1045     bool npWaitForLoginDone;
1046 };
1047 
1048 static Q3PtrDict<Q3FtpPrivate> *d_ptr = 0;
cleanup_d_ptr()1049 static void cleanup_d_ptr()
1050 {
1051     delete d_ptr;
1052     d_ptr = 0;
1053 }
dHelper(const Q3Ftp * foo)1054 static Q3FtpPrivate* dHelper( const Q3Ftp* foo )
1055 {
1056     if ( !d_ptr ) {
1057 	d_ptr = new Q3PtrDict<Q3FtpPrivate>;
1058 	d_ptr->setAutoDelete( true );
1059 	qAddPostRoutine( cleanup_d_ptr );
1060     }
1061     Q3FtpPrivate* ret = d_ptr->find( (void*)foo );
1062     if ( ! ret ) {
1063 	ret = new Q3FtpPrivate;
1064 	d_ptr->replace( (void*) foo, ret );
1065     }
1066     return ret;
1067 }
1068 
delete_d(const Q3Ftp * foo)1069 static void delete_d( const Q3Ftp* foo )
1070 {
1071     if ( d_ptr )
1072 	d_ptr->remove( (void*) foo );
1073 }
1074 
1075 /**********************************************************************
1076  *
1077  * Q3Ftp implementation
1078  *
1079  *********************************************************************/
1080 /*!
1081     \class Q3Ftp
1082     \brief The Q3Ftp class provides an implementation of the FTP protocol.
1083 
1084     \compat
1085 
1086     This class provides two different interfaces: one is the
1087     QNetworkProtocol interface that allows you to use FTP through the
1088     QUrlOperator abstraction. The other is a direct interface to FTP
1089     that gives you lower-level access to the FTP protocol for finer
1090     control. Using the direct interface you can also execute arbitrary
1091     FTP commands.
1092 
1093     Don't mix the two interfaces, since the behavior is not
1094     well-defined.
1095 
1096     If you want to use Q3Ftp with the QNetworkProtocol interface, you
1097     do not use it directly, but rather through a QUrlOperator, for
1098     example:
1099 
1100     \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 0
1101 
1102     This code will only work if the Q3Ftp class is registered; to
1103     register the class, you must call q3InitNetworkProtocols() before
1104     using a QUrlOperator with Q3Ftp.
1105 
1106     The rest of this descrption describes the direct interface to FTP.
1107 
1108     The class works asynchronously, so there are no blocking
1109     functions. If an operation cannot be executed immediately, the
1110     function will still return straight away and the operation will be
1111     scheduled for later execution. The results of scheduled operations
1112     are reported via signals. This approach depends on the event loop
1113     being in operation.
1114 
1115     The operations that can be scheduled (they are called "commands"
1116     in the rest of the documentation) are the following:
1117     connectToHost(), login(), close(), list(), cd(), get(), put(),
1118     remove(), mkdir(), rmdir(), rename() and rawCommand().
1119 
1120     All of these commands return a unique identifier that allows you
1121     to keep track of the command that is currently being executed.
1122     When the execution of a command starts, the commandStarted()
1123     signal with the command's identifier is emitted. When the command
1124     is finished, the commandFinished() signal is emitted with the
1125     command's identifier and a bool that indicates whether the command
1126     finished with an error.
1127 
1128     In some cases, you might want to execute a sequence of commands,
1129     e.g. if you want to connect and login to a FTP server. This is
1130     simply achieved:
1131 
1132     \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 1
1133 
1134     In this case two FTP commands have been scheduled. When the last
1135     scheduled command has finished, a done() signal is emitted with
1136     a bool argument that tells you whether the sequence finished with
1137     an error.
1138 
1139     If an error occurs during the execution of one of the commands in
1140     a sequence of commands, all the pending commands (i.e. scheduled,
1141     but not yet executed commands) are cleared and no signals are
1142     emitted for them.
1143 
1144     Some commands, e.g. list(), emit additional signals to report
1145     their results.
1146 
1147     Example: If you want to download the INSTALL file from the Qt
1148     FTP server, you would write this:
1149 
1150     \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 2
1151 
1152     For this example the following sequence of signals is emitted
1153     (with small variations, depending on network traffic, etc.):
1154 
1155     \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 3
1156 
1157     The dataTransferProgress() signal in the above example is useful
1158     if you want to show a \link QProgressBar progress bar \endlink to
1159     inform the user about the progress of the download. The
1160     readyRead() signal tells you that there is data ready to be read.
1161     The amount of data can be queried then with the bytesAvailable()
1162     function and it can be read with the readBlock() or readAll()
1163     function.
1164 
1165     If the login fails for the above example, the signals would look
1166     like this:
1167 
1168     \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 4
1169 
1170     You can then get details about the error with the error() and
1171     errorString() functions.
1172 
1173     The functions currentId() and currentCommand() provide more
1174     information about the currently executing command.
1175 
1176     The functions hasPendingCommands() and clearPendingCommands()
1177     allow you to query and clear the list of pending commands.
1178 
1179     The safest and easiest way to use the FTP protocol is to use
1180     QUrlOperator() or the FTP commands described above. If you are an
1181     experienced network programmer and want to have complete control
1182     you can use rawCommand() to execute arbitrary FTP commands.
1183 
1184     \sa Q3NetworkProtocol, Q3UrlOperator Q3Http
1185 */
1186 
1187 /*!
1188     Constructs a Q3Ftp object.
1189 */
Q3Ftp()1190 Q3Ftp::Q3Ftp() : Q3NetworkProtocol()
1191 {
1192     init();
1193 }
1194 
1195 /*!
1196     Constructs a Q3Ftp object. The \a parent and \a name parameters
1197     are passed to the QObject constructor.
1198 */
Q3Ftp(QObject * parent,const char * name)1199 Q3Ftp::Q3Ftp( QObject *parent, const char *name ) : Q3NetworkProtocol()
1200 {
1201     if ( parent )
1202 	parent->insertChild( this );
1203     setName( name );
1204     init();
1205 }
1206 
init()1207 void Q3Ftp::init()
1208 {
1209     Q3FtpPrivate *d = dHelper( this );
1210     d->errorString = QFtp::tr( "Unknown error" );
1211 
1212     connect( &d->pi, SIGNAL(connectState(int)),
1213 	    SLOT(piConnectState(int)) );
1214     connect( &d->pi, SIGNAL(finished(QString)),
1215 	    SLOT(piFinished(QString)) );
1216     connect( &d->pi, SIGNAL(error(int,QString)),
1217 	    SLOT(piError(int,QString)) );
1218     connect( &d->pi, SIGNAL(rawFtpReply(int,QString)),
1219 	    SLOT(piFtpReply(int,QString)) );
1220 
1221     connect( &d->pi.dtp, SIGNAL(readyRead()),
1222 	    SIGNAL(readyRead()) );
1223     connect( &d->pi.dtp, SIGNAL(dataTransferProgress(int,int)),
1224 	    SIGNAL(dataTransferProgress(int,int)) );
1225     connect( &d->pi.dtp, SIGNAL(listInfo(QUrlInfo)),
1226 	    SIGNAL(listInfo(QUrlInfo)) );
1227 }
1228 
1229 /*!
1230     \enum Q3Ftp::State
1231 
1232     This enum defines the connection state:
1233 
1234     \value Unconnected There is no connection to the host.
1235     \value HostLookup A host name lookup is in progress.
1236     \value Connecting An attempt to connect to the host is in progress.
1237     \value Connected Connection to the host has been achieved.
1238     \value LoggedIn Connection and user login have been achieved.
1239     \value Closing The connection is closing down, but it is not yet
1240     closed. (The state will be \c Unconnected when the connection is
1241     closed.)
1242 
1243     \sa stateChanged() state()
1244 */
1245 /*!
1246     \enum Q3Ftp::Error
1247 
1248     This enum identifies the error that occurred.
1249 
1250     \value NoError No error occurred.
1251     \value HostNotFound The host name lookup failed.
1252     \value ConnectionRefused The server refused the connection.
1253     \value NotConnected Tried to send a command, but there is no connection to
1254     a server.
1255     \value UnknownError An error other than those specified above
1256     occurred.
1257 
1258     \sa error()
1259 */
1260 
1261 /*!
1262     \enum Q3Ftp::Command
1263 
1264     This enum is used as the return value for the currentCommand() function.
1265     This allows you to perform specific actions for particular
1266     commands, e.g. in a FTP client, you might want to clear the
1267     directory view when a list() command is started; in this case you
1268     can simply check in the slot connected to the start() signal if
1269     the currentCommand() is \c List.
1270 
1271     \value None No command is being executed.
1272     \value ConnectToHost connectToHost() is being executed.
1273     \value Login login() is being executed.
1274     \value Close close() is being executed.
1275     \value List list() is being executed.
1276     \value Cd cd() is being executed.
1277     \value Get get() is being executed.
1278     \value Put put() is being executed.
1279     \value Remove remove() is being executed.
1280     \value Mkdir mkdir() is being executed.
1281     \value Rmdir rmdir() is being executed.
1282     \value Rename rename() is being executed.
1283     \value RawCommand rawCommand() is being executed.
1284 
1285     \sa currentCommand()
1286 */
1287 
1288 /*!
1289     \fn void Q3Ftp::stateChanged( int state )
1290 
1291     This signal is emitted when the state of the connection changes.
1292     The argument \a state is the new state of the connection; it is
1293     one of the \l State values.
1294 
1295     It is usually emitted in response to a connectToHost() or close()
1296     command, but it can also be emitted "spontaneously", e.g. when the
1297     server closes the connection unexpectedly.
1298 
1299     \sa connectToHost() close() state() State
1300 */
1301 
1302 /*!
1303     \fn void Q3Ftp::listInfo( const QUrlInfo &i );
1304 
1305     This signal is emitted for each directory entry the list() command
1306     finds. The details of the entry are stored in \a i.
1307 
1308     \sa list()
1309 */
1310 
1311 /*!
1312     \fn void Q3Ftp::commandStarted( int id )
1313 
1314     This signal is emitted when processing the command identified by
1315     \a id starts.
1316 
1317     \sa commandFinished() done()
1318 */
1319 
1320 /*!
1321     \fn void Q3Ftp::commandFinished( int id, bool error )
1322 
1323     This signal is emitted when processing the command identified by
1324     \a id has finished. \a error is true if an error occurred during
1325     the processing; otherwise \a error is false.
1326 
1327     \sa commandStarted() done() error() errorString()
1328 */
1329 
1330 /*!
1331     \fn void Q3Ftp::done( bool error )
1332 
1333     This signal is emitted when the last pending command has finished;
1334     (it is emitted after the last command's commandFinished() signal).
1335     \a error is true if an error occurred during the processing;
1336     otherwise \a error is false.
1337 
1338     \sa commandFinished() error() errorString()
1339 */
1340 
1341 /*!
1342     \fn void Q3Ftp::readyRead()
1343 
1344     This signal is emitted in response to a get() command when there
1345     is new data to read.
1346 
1347     If you specify a device as the second argument in the get()
1348     command, this signal is \e not emitted; instead the data is
1349     written directly to the device.
1350 
1351     You can read the data with the readAll() or readBlock() functions.
1352 
1353     This signal is useful if you want to process the data in chunks as
1354     soon as it becomes available. If you are only interested in the
1355     complete data, just connect to the commandFinished() signal and
1356     read the data then instead.
1357 
1358     \sa get() readBlock() readAll() bytesAvailable()
1359 */
1360 
1361 /*!
1362     \fn void Q3Ftp::dataTransferProgress( int done, int total )
1363 
1364     This signal is emitted in response to a get() or put() request to
1365     indicate the current progress of the download or upload.
1366 
1367     \a done is the amount of data that has already been transferred
1368     and \a total is the total amount of data to be read or written. It
1369     is possible that the Q3Ftp class is not able to determine the total
1370     amount of data that should be transferred, in which case \a total
1371     is 0. (If you connect this signal to a QProgressBar, the progress
1372     bar shows a busy indicator if the total is 0).
1373 
1374     \warning \a done and \a total are not necessarily the size in
1375     bytes, since for large files these values might need to be
1376     "scaled" to avoid overflow.
1377 
1378     \sa get() put()
1379 */
1380 
1381 /*!
1382     \fn void Q3Ftp::rawCommandReply( int replyCode, const QString &detail );
1383 
1384     This signal is emitted in response to the rawCommand() function.
1385     \a replyCode is the 3 digit reply code and \a detail is the text
1386     that follows the reply code.
1387 
1388     \sa rawCommand()
1389 */
1390 
1391 /*!
1392     Connects to the FTP server \a host using port \a port.
1393 
1394     The stateChanged() signal is emitted when the state of the
1395     connecting process changes, e.g. to \c HostLookup, then \c
1396     Connecting, then \c Connected.
1397 
1398     The function does not block and returns immediately. The command
1399     is scheduled, and its execution is performed asynchronously. The
1400     function returns a unique identifier which is passed by
1401     commandStarted() and commandFinished().
1402 
1403     When the command is started the commandStarted() signal is
1404     emitted. When it is finished the commandFinished() signal is
1405     emitted.
1406 
1407     \sa stateChanged() commandStarted() commandFinished()
1408 */
connectToHost(const QString & host,Q_UINT16 port)1409 int Q3Ftp::connectToHost( const QString &host, Q_UINT16 port )
1410 {
1411     QStringList cmds;
1412     cmds << host;
1413     cmds << QString::number( (uint)port );
1414     return addCommand( new Q3FtpCommand( ConnectToHost, cmds ) );
1415 }
1416 
1417 /*!
1418     Logs in to the FTP server with the username \a user and the
1419     password \a password.
1420 
1421     The stateChanged() signal is emitted when the state of the
1422     connecting process changes, e.g. to \c LoggedIn.
1423 
1424     The function does not block and returns immediately. The command
1425     is scheduled, and its execution is performed asynchronously. The
1426     function returns a unique identifier which is passed by
1427     commandStarted() and commandFinished().
1428 
1429     When the command is started the commandStarted() signal is
1430     emitted. When it is finished the commandFinished() signal is
1431     emitted.
1432 
1433     \sa commandStarted() commandFinished()
1434 */
login(const QString & user,const QString & password)1435 int Q3Ftp::login( const QString &user, const QString &password )
1436 {
1437     QStringList cmds;
1438     cmds << ( QString::fromLatin1("USER ") + ( user.isNull() ? QString::fromLatin1("anonymous") : user ) + QLatin1String("\r\n") );
1439     cmds << ( QString::fromLatin1("PASS ") + ( password.isNull() ? QString::fromLatin1("anonymous@") : password ) + QLatin1String("\r\n") );
1440     return addCommand( new Q3FtpCommand( Login, cmds ) );
1441 }
1442 
1443 /*!
1444     Closes the connection to the FTP server.
1445 
1446     The stateChanged() signal is emitted when the state of the
1447     connecting process changes, e.g. to \c Closing, then \c
1448     Unconnected.
1449 
1450     The function does not block and returns immediately. The command
1451     is scheduled, and its execution is performed asynchronously. The
1452     function returns a unique identifier which is passed by
1453     commandStarted() and commandFinished().
1454 
1455     When the command is started the commandStarted() signal is
1456     emitted. When it is finished the commandFinished() signal is
1457     emitted.
1458 
1459     \sa stateChanged() commandStarted() commandFinished()
1460 */
close()1461 int Q3Ftp::close()
1462 {
1463     return addCommand( new Q3FtpCommand( Close, QStringList(QLatin1String("QUIT\r\n")) ) );
1464 }
1465 
1466 /*!
1467     Lists the contents of directory \a dir on the FTP server. If \a
1468     dir is empty, it lists the contents of the current directory.
1469 
1470     The listInfo() signal is emitted for each directory entry found.
1471 
1472     The function does not block and returns immediately. The command
1473     is scheduled, and its execution is performed asynchronously. The
1474     function returns a unique identifier which is passed by
1475     commandStarted() and commandFinished().
1476 
1477     When the command is started the commandStarted() signal is
1478     emitted. When it is finished the commandFinished() signal is
1479     emitted.
1480 
1481     \sa listInfo() commandStarted() commandFinished()
1482 */
list(const QString & dir)1483 int Q3Ftp::list( const QString &dir )
1484 {
1485     QStringList cmds;
1486     cmds << QLatin1String("TYPE A\r\n");
1487     cmds << QLatin1String("PASV\r\n");
1488     if ( dir.isEmpty() )
1489 	cmds << QLatin1String("LIST\r\n");
1490     else
1491 	cmds << ( QLatin1String("LIST ") + dir + QLatin1String("\r\n") );
1492     return addCommand( new Q3FtpCommand( List, cmds ) );
1493 }
1494 
1495 /*!
1496     Changes the working directory of the server to \a dir.
1497 
1498     The function does not block and returns immediately. The command
1499     is scheduled, and its execution is performed asynchronously. The
1500     function returns a unique identifier which is passed by
1501     commandStarted() and commandFinished().
1502 
1503     When the command is started the commandStarted() signal is
1504     emitted. When it is finished the commandFinished() signal is
1505     emitted.
1506 
1507     \sa commandStarted() commandFinished()
1508 */
cd(const QString & dir)1509 int Q3Ftp::cd( const QString &dir )
1510 {
1511     return addCommand( new Q3FtpCommand( Cd, QStringList(QLatin1String("CWD ")+dir+QLatin1String("\r\n")) ) );
1512 }
1513 
1514 /*!
1515     Downloads the file \a file from the server.
1516 
1517     If \a dev is 0, then the readyRead() signal is emitted when there
1518     is data available to read. You can then read the data with the
1519     readBlock() or readAll() functions.
1520 
1521     If \a dev is not 0, the data is written directly to the device \a
1522     dev. Make sure that the \a dev pointer is valid for the duration
1523     of the operation (it is safe to delete it when the
1524     commandFinished() signal is emitted). In this case the readyRead()
1525     signal is \e not emitted and you cannot read data with the
1526     readBlock() or readAll() functions.
1527 
1528     If you don't read the data immediately it becomes available, i.e.
1529     when the readyRead() signal is emitted, it is still available
1530     until the next command is started.
1531 
1532     For example, if you want to present the data to the user as soon
1533     as there is something available, connect to the readyRead() signal
1534     and read the data immediately. On the other hand, if you only want
1535     to work with the complete data, you can connect to the
1536     commandFinished() signal and read the data when the get() command
1537     is finished.
1538 
1539     The function does not block and returns immediately. The command
1540     is scheduled, and its execution is performed asynchronously. The
1541     function returns a unique identifier which is passed by
1542     commandStarted() and commandFinished().
1543 
1544     When the command is started the commandStarted() signal is
1545     emitted. When it is finished the commandFinished() signal is
1546     emitted.
1547 
1548     \sa readyRead() dataTransferProgress() commandStarted()
1549     commandFinished()
1550 */
get(const QString & file,QIODevice * dev)1551 int Q3Ftp::get( const QString &file, QIODevice *dev )
1552 {
1553     QStringList cmds;
1554     cmds << ( QLatin1String("SIZE ") + file + QLatin1String("\r\n") );
1555     cmds << QLatin1String("TYPE I\r\n");
1556     cmds << QLatin1String("PASV\r\n");
1557     cmds << ( QLatin1String("RETR ") + file + QLatin1String("\r\n") );
1558     if ( dev )
1559 	return addCommand( new Q3FtpCommand( Get, cmds, dev ) );
1560     return addCommand( new Q3FtpCommand( Get, cmds ) );
1561 }
1562 
1563 /*!
1564     \overload
1565 
1566     Writes the data \a data to the file called \a file on the server.
1567     The progress of the upload is reported by the
1568     dataTransferProgress() signal.
1569 
1570     The function does not block and returns immediately. The command
1571     is scheduled, and its execution is performed asynchronously. The
1572     function returns a unique identifier which is passed by
1573     commandStarted() and commandFinished().
1574 
1575     When the command is started the commandStarted() signal is
1576     emitted. When it is finished the commandFinished() signal is
1577     emitted.
1578 
1579     \sa dataTransferProgress() commandStarted() commandFinished()
1580 */
put(const QByteArray & data,const QString & file)1581 int Q3Ftp::put( const QByteArray &data, const QString &file )
1582 {
1583     QStringList cmds;
1584     cmds << QLatin1String("TYPE I\r\n");
1585     cmds << QLatin1String("PASV\r\n");
1586     cmds << ( QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n") );
1587     cmds << ( QLatin1String("STOR ") + file + QLatin1String("\r\n") );
1588     return addCommand( new Q3FtpCommand( Put, cmds, data ) );
1589 }
1590 
1591 /*!
1592     Reads the data from the IO device \a dev, and writes it to the
1593     file called \a file on the server. The data is read in chunks from
1594     the IO device, so this overload allows you to transmit large
1595     amounts of data without the need to read all the data into memory
1596     at once.
1597 
1598     Make sure that the \a dev pointer is valid for the duration of the
1599     operation (it is safe to delete it when the commandFinished() is
1600     emitted).
1601 */
put(QIODevice * dev,const QString & file)1602 int Q3Ftp::put( QIODevice *dev, const QString &file )
1603 {
1604     QStringList cmds;
1605     cmds << QLatin1String("TYPE I\r\n");
1606     cmds << QLatin1String("PASV\r\n");
1607     if ( !dev->isSequentialAccess() )
1608 	cmds << ( QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n") );
1609     cmds << ( QLatin1String("STOR ") + file + QLatin1String("\r\n") );
1610     return addCommand( new Q3FtpCommand( Put, cmds, dev ) );
1611 }
1612 
1613 /*!
1614     Deletes the file called \a file from the server.
1615 
1616     The function does not block and returns immediately. The command
1617     is scheduled, and its execution is performed asynchronously. The
1618     function returns a unique identifier which is passed by
1619     commandStarted() and commandFinished().
1620 
1621     When the command is started the commandStarted() signal is
1622     emitted. When it is finished the commandFinished() signal is
1623     emitted.
1624 
1625     \sa commandStarted() commandFinished()
1626 */
remove(const QString & file)1627 int Q3Ftp::remove( const QString &file )
1628 {
1629     return addCommand( new Q3FtpCommand( Remove, QStringList(QLatin1String("DELE ")+file+QLatin1String("\r\n")) ) );
1630 }
1631 
1632 /*!
1633     Creates a directory called \a dir on the server.
1634 
1635     The function does not block and returns immediately. The command
1636     is scheduled, and its execution is performed asynchronously. The
1637     function returns a unique identifier which is passed by
1638     commandStarted() and commandFinished().
1639 
1640     When the command is started the commandStarted() signal is
1641     emitted. When it is finished the commandFinished() signal is
1642     emitted.
1643 
1644     \sa commandStarted() commandFinished()
1645 */
mkdir(const QString & dir)1646 int Q3Ftp::mkdir( const QString &dir )
1647 {
1648     return addCommand( new Q3FtpCommand( Mkdir, QStringList(QLatin1String("MKD ")+dir+QLatin1String("\r\n")) ) );
1649 }
1650 
1651 /*!
1652     Removes the directory called \a dir from the server.
1653 
1654     The function does not block and returns immediately. The command
1655     is scheduled, and its execution is performed asynchronously. The
1656     function returns a unique identifier which is passed by
1657     commandStarted() and commandFinished().
1658 
1659     When the command is started the commandStarted() signal is
1660     emitted. When it is finished the commandFinished() signal is
1661     emitted.
1662 
1663     \sa commandStarted() commandFinished()
1664 */
rmdir(const QString & dir)1665 int Q3Ftp::rmdir( const QString &dir )
1666 {
1667     return addCommand( new Q3FtpCommand( Rmdir, QStringList(QLatin1String("RMD ")+dir+QLatin1String("\r\n")) ) );
1668 }
1669 
1670 /*!
1671     Renames the file called \a oldname to \a newname on the server.
1672 
1673     The function does not block and returns immediately. The command
1674     is scheduled, and its execution is performed asynchronously. The
1675     function returns a unique identifier which is passed by
1676     commandStarted() and commandFinished().
1677 
1678     When the command is started the commandStarted() signal is
1679     emitted. When it is finished the commandFinished() signal is
1680     emitted.
1681 
1682     \sa commandStarted() commandFinished()
1683 */
rename(const QString & oldname,const QString & newname)1684 int Q3Ftp::rename( const QString &oldname, const QString &newname )
1685 {
1686     QStringList cmds;
1687     cmds << ( QLatin1String("RNFR ") + oldname + QLatin1String("\r\n") );
1688     cmds << ( QLatin1String("RNTO ") + newname + QLatin1String("\r\n") );
1689     return addCommand( new Q3FtpCommand( Rename, cmds ) );
1690 }
1691 
1692 /*!
1693     Sends the raw FTP command \a command to the FTP server. This is
1694     useful for low-level FTP access. If the operation you wish to
1695     perform has an equivalent Q3Ftp function, we recommend using the
1696     function instead of raw FTP commands since the functions are
1697     easier and safer.
1698 
1699     The function does not block and returns immediately. The command
1700     is scheduled, and its execution is performed asynchronously. The
1701     function returns a unique identifier which is passed by
1702     commandStarted() and commandFinished().
1703 
1704     When the command is started the commandStarted() signal is
1705     emitted. When it is finished the commandFinished() signal is
1706     emitted.
1707 
1708     \sa rawCommandReply() commandStarted() commandFinished()
1709 */
rawCommand(const QString & command)1710 int Q3Ftp::rawCommand( const QString &command )
1711 {
1712     QString cmd = command.stripWhiteSpace() + QLatin1String("\r\n");
1713     return addCommand( new Q3FtpCommand( RawCommand, QStringList(cmd) ) );
1714 }
1715 
1716 /*!
1717     Returns the number of bytes that can be read from the data socket
1718     at the moment.
1719 
1720     \sa get() readyRead() readBlock() readAll()
1721 */
bytesAvailable() const1722 Q_ULONG Q3Ftp::bytesAvailable() const
1723 {
1724     Q3FtpPrivate *d = dHelper( this );
1725     return d->pi.dtp.bytesAvailable();
1726 }
1727 
1728 /*!
1729     Reads \a maxlen bytes from the data socket into \a data and
1730     returns the number of bytes read. Returns -1 if an error occurred.
1731 
1732     \sa get() readyRead() bytesAvailable() readAll()
1733 */
readBlock(char * data,Q_ULONG maxlen)1734 Q_LONG Q3Ftp::readBlock( char *data, Q_ULONG maxlen )
1735 {
1736     Q3FtpPrivate *d = dHelper( this );
1737     return d->pi.dtp.readBlock( data, maxlen );
1738 }
1739 
1740 /*!
1741     Reads all the bytes available from the data socket and returns
1742     them.
1743 
1744     \sa get() readyRead() bytesAvailable() readBlock()
1745 */
readAll()1746 QByteArray Q3Ftp::readAll()
1747 {
1748     Q3FtpPrivate *d = dHelper( this );
1749     return d->pi.dtp.readAll();
1750 }
1751 
1752 /*!
1753     Aborts the current command and deletes all scheduled commands.
1754 
1755     If there is an unfinished command (i.e. a command for which the
1756     commandStarted() signal has been emitted, but for which the
1757     commandFinished() signal has not been emitted), this function
1758     sends an \c ABORT command to the server. When the server replies
1759     that the command is aborted, the commandFinished() signal with the
1760     \c error argument set to \c true is emitted for the command. Due
1761     to timing issues, it is possible that the command had already
1762     finished before the abort request reached the server, in which
1763     case, the commandFinished() signal is emitted with the \c error
1764     argument set to \c false.
1765 
1766     For all other commands that are affected by the abort(), no
1767     signals are emitted.
1768 
1769     If you don't start further FTP commands directly after the
1770     abort(), there won't be any scheduled commands and the done()
1771     signal is emitted.
1772 
1773     \warning Some FTP servers, for example the BSD FTP daemon (version
1774     0.3), wrongly return a positive reply even when an abort has
1775     occurred. For these servers the commandFinished() signal has its
1776     error flag set to \c false, even though the command did not
1777     complete successfully.
1778 
1779     \sa clearPendingCommands()
1780 */
abort()1781 void Q3Ftp::abort()
1782 {
1783     Q3FtpPrivate *d = dHelper( this );
1784     if ( d->pending.isEmpty() )
1785 	return;
1786 
1787     clearPendingCommands();
1788     d->pi.abort();
1789 }
1790 
1791 /*!
1792     Returns the identifier of the FTP command that is being executed
1793     or 0 if there is no command being executed.
1794 
1795     \sa currentCommand()
1796 */
currentId() const1797 int Q3Ftp::currentId() const
1798 {
1799     Q3FtpPrivate *d = dHelper( this );
1800     Q3FtpCommand *c = d->pending.getFirst();
1801     if ( c == 0 )
1802 	return 0;
1803     return c->id;
1804 }
1805 
1806 /*!
1807     Returns the command type of the FTP command being executed or \c
1808     None if there is no command being executed.
1809 
1810     \sa currentId()
1811 */
currentCommand() const1812 Q3Ftp::Command Q3Ftp::currentCommand() const
1813 {
1814     Q3FtpPrivate *d = dHelper( this );
1815     Q3FtpCommand *c = d->pending.getFirst();
1816     if ( c == 0 )
1817 	return None;
1818     return c->command;
1819 }
1820 
1821 /*!
1822     Returns the QIODevice pointer that is used by the FTP command to read data
1823     from or store data to. If there is no current FTP command being executed or
1824     if the command does not use an IO device, this function returns 0.
1825 
1826     This function can be used to delete the QIODevice in the slot connected to
1827     the commandFinished() signal.
1828 
1829     \sa get() put()
1830 */
currentDevice() const1831 QIODevice* Q3Ftp::currentDevice() const
1832 {
1833     Q3FtpPrivate *d = dHelper( this );
1834     Q3FtpCommand *c = d->pending.getFirst();
1835     if ( !c )
1836 	return 0;
1837     if ( c->is_ba )
1838 	return 0;
1839     return c->data.dev;
1840 }
1841 
1842 /*!
1843     Returns true if there are any commands scheduled that have not yet
1844     been executed; otherwise returns false.
1845 
1846     The command that is being executed is \e not considered as a
1847     scheduled command.
1848 
1849     \sa clearPendingCommands() currentId() currentCommand()
1850 */
hasPendingCommands() const1851 bool Q3Ftp::hasPendingCommands() const
1852 {
1853     Q3FtpPrivate *d = dHelper( this );
1854     return d->pending.count() > 1;
1855 }
1856 
1857 /*!
1858     Deletes all pending commands from the list of scheduled commands.
1859     This does not affect the command that is being executed. If you
1860     want to stop this as well, use abort().
1861 
1862     \sa hasPendingCommands() abort()
1863 */
clearPendingCommands()1864 void Q3Ftp::clearPendingCommands()
1865 {
1866     Q3FtpPrivate *d = dHelper( this );
1867     Q3FtpCommand *c = 0;
1868     if ( d->pending.count() > 0 )
1869 	c = d->pending.take( 0 );
1870     d->pending.clear();
1871     if ( c )
1872 	d->pending.append( c );
1873 }
1874 
1875 /*!
1876     Returns the current state of the object. When the state changes,
1877     the stateChanged() signal is emitted.
1878 
1879     \sa State stateChanged()
1880 */
state() const1881 Q3Ftp::State Q3Ftp::state() const
1882 {
1883     Q3FtpPrivate *d = dHelper( this );
1884     return d->state;
1885 }
1886 
1887 /*!
1888     Returns the last error that occurred. This is useful to find out
1889     what when wrong when receiving a commandFinished() or a done()
1890     signal with the \c error argument set to \c true.
1891 
1892     If you start a new command, the error status is reset to \c NoError.
1893 */
error() const1894 Q3Ftp::Error Q3Ftp::error() const
1895 {
1896     Q3FtpPrivate *d = dHelper( this );
1897     return d->error;
1898 }
1899 
1900 /*!
1901     Returns a human-readable description of the last error that
1902     occurred. This is useful for presenting a error message to the
1903     user when receiving a commandFinished() or a done() signal with
1904     the \c error argument set to \c true.
1905 
1906     The error string is often (but not always) the reply from the
1907     server, so it is not always possible to translate the string. If
1908     the message comes from Qt, the string has already passed through
1909     tr().
1910 */
errorString() const1911 QString Q3Ftp::errorString() const
1912 {
1913     Q3FtpPrivate *d = dHelper( this );
1914     return d->errorString;
1915 }
1916 
addCommand(Q3FtpCommand * cmd)1917 int Q3Ftp::addCommand( Q3FtpCommand *cmd )
1918 {
1919     Q3FtpPrivate *d = dHelper( this );
1920     d->pending.append( cmd );
1921 
1922     if ( d->pending.count() == 1 )
1923 	// don't emit the commandStarted() signal before the id is returned
1924 	QTimer::singleShot( 0, this, SLOT(startNextCommand()) );
1925 
1926     return cmd->id;
1927 }
1928 
startNextCommand()1929 void Q3Ftp::startNextCommand()
1930 {
1931     Q3FtpPrivate *d = dHelper( this );
1932 
1933     Q3FtpCommand *c = d->pending.getFirst();
1934     if ( c == 0 )
1935 	return;
1936 
1937     d->error = NoError;
1938     d->errorString = QFtp::tr( "Unknown error" );
1939 
1940     if ( bytesAvailable() )
1941 	readAll(); // clear the data
1942     emit commandStarted( c->id );
1943 
1944     if ( c->command == ConnectToHost ) {
1945 	d->pi.connectToHost( c->rawCmds[0], c->rawCmds[1].toUInt() );
1946     } else {
1947 	if ( c->command == Put ) {
1948 	    if ( c->is_ba ) {
1949 		d->pi.dtp.setData( c->data.ba );
1950 		d->pi.dtp.setBytesTotal( c->data.ba->size() );
1951 	    } else if ( c->data.dev && (c->data.dev->isOpen() || c->data.dev->open(QIODevice::ReadOnly)) ) {
1952 		d->pi.dtp.setDevice( c->data.dev );
1953 		if ( c->data.dev->isSequentialAccess() )
1954 		    d->pi.dtp.setBytesTotal( 0 );
1955 		else
1956 		    d->pi.dtp.setBytesTotal( c->data.dev->size() );
1957 	    }
1958 	} else if ( c->command == Get ) {
1959 	    if ( !c->is_ba && c->data.dev ) {
1960 		d->pi.dtp.setDevice( c->data.dev );
1961 	    }
1962 	} else if ( c->command == Close ) {
1963 	    d->state = Q3Ftp::Closing;
1964 	    emit stateChanged( d->state );
1965 	}
1966 	if ( !d->pi.sendCommands( c->rawCmds ) ) {
1967 	    // ### error handling (this case should not happen)
1968 	}
1969     }
1970 }
1971 
piFinished(const QString &)1972 void Q3Ftp::piFinished( const QString& )
1973 {
1974     Q3FtpPrivate *d = dHelper( this );
1975     Q3FtpCommand *c = d->pending.getFirst();
1976     if ( c == 0 )
1977 	return;
1978 
1979     if ( c->command == Close ) {
1980 	// The order of in which the slots are called is arbitrary, so
1981 	// disconnect the SIGNAL-SIGNAL temporary to make sure that we
1982 	// don't get the commandFinished() signal before the stateChanged()
1983 	// signal.
1984 	if ( d->state != Q3Ftp::Unconnected ) {
1985 	    d->close_waitForStateChange = true;
1986 	    return;
1987 	}
1988     }
1989     emit commandFinished( c->id, false );
1990 
1991     d->pending.removeFirst();
1992     if ( d->pending.isEmpty() ) {
1993 	emit done( false );
1994     } else {
1995 	startNextCommand();
1996     }
1997 }
1998 
piError(int errorCode,const QString & text)1999 void Q3Ftp::piError( int errorCode, const QString &text )
2000 {
2001     Q3FtpPrivate *d = dHelper( this );
2002     Q3FtpCommand *c = d->pending.getFirst();
2003 
2004     // non-fatal errors
2005     if ( c->command==Get && d->pi.currentCommand().startsWith(QLatin1String("SIZE ")) ) {
2006 	d->pi.dtp.setBytesTotal( -1 );
2007 	return;
2008     } else if ( c->command==Put && d->pi.currentCommand().startsWith(QLatin1String("ALLO ")) ) {
2009 	return;
2010     }
2011 
2012     d->error = (Error)errorCode;
2013     switch ( currentCommand() ) {
2014 	case ConnectToHost:
2015 	    d->errorString = QFtp::tr( "Connecting to host failed:\n%1" ).arg( text );
2016 	    break;
2017 	case Login:
2018 	    d->errorString = QFtp::tr( "Login failed:\n%1" ).arg( text );
2019 	    break;
2020 	case List:
2021 	    d->errorString = QFtp::tr( "Listing directory failed:\n%1" ).arg( text );
2022 	    break;
2023 	case Cd:
2024 	    d->errorString = QFtp::tr( "Changing directory failed:\n%1" ).arg( text );
2025 	    break;
2026 	case Get:
2027 	    d->errorString = QFtp::tr( "Downloading file failed:\n%1" ).arg( text );
2028 	    break;
2029 	case Put:
2030 	    d->errorString = QFtp::tr( "Uploading file failed:\n%1" ).arg( text );
2031 	    break;
2032 	case Remove:
2033 	    d->errorString = QFtp::tr( "Removing file failed:\n%1" ).arg( text );
2034 	    break;
2035 	case Mkdir:
2036 	    d->errorString = QFtp::tr( "Creating directory failed:\n%1" ).arg( text );
2037 	    break;
2038 	case Rmdir:
2039 	    d->errorString = QFtp::tr( "Removing directory failed:\n%1" ).arg( text );
2040 	    break;
2041 	default:
2042 	    d->errorString = text;
2043 	    break;
2044     }
2045 
2046     d->pi.clearPendingCommands();
2047     clearPendingCommands();
2048     emit commandFinished( c->id, true );
2049 
2050     d->pending.removeFirst();
2051     if ( d->pending.isEmpty() )
2052 	emit done( true );
2053     else
2054 	startNextCommand();
2055 }
2056 
piConnectState(int state)2057 void Q3Ftp::piConnectState( int state )
2058 {
2059     Q3FtpPrivate *d = dHelper( this );
2060     d->state = (State)state;
2061     emit stateChanged( d->state );
2062     if ( d->close_waitForStateChange ) {
2063 	d->close_waitForStateChange = false;
2064 	piFinished( QFtp::tr( "Connection closed" ) );
2065     }
2066 }
2067 
piFtpReply(int code,const QString & text)2068 void Q3Ftp::piFtpReply( int code, const QString &text )
2069 {
2070     if ( currentCommand() == RawCommand ) {
2071 	Q3FtpPrivate *d = dHelper( this );
2072 	d->pi.rawCommand = true;
2073 	emit rawCommandReply( code, text );
2074     }
2075 }
2076 
2077 /*!
2078     Destructor.
2079 */
~Q3Ftp()2080 Q3Ftp::~Q3Ftp()
2081 {
2082     abort();
2083     close();
2084     delete_d( this );
2085 }
2086 
2087 /**********************************************************************
2088  *
2089  * Q3Ftp implementation of the Q3NetworkProtocol interface
2090  *
2091  *********************************************************************/
2092 /*!  \reimp
2093 */
operationListChildren(Q3NetworkOperation * op)2094 void Q3Ftp::operationListChildren( Q3NetworkOperation *op )
2095 {
2096     op->setState( StInProgress );
2097 
2098     cd( ( url()->path().isEmpty() ? QString::fromLatin1("/") : url()->path() ) );
2099     list();
2100     emit start( op );
2101 }
2102 
2103 /*!  \reimp
2104 */
operationMkDir(Q3NetworkOperation * op)2105 void Q3Ftp::operationMkDir( Q3NetworkOperation *op )
2106 {
2107     op->setState( StInProgress );
2108 
2109     mkdir( op->arg( 0 ) );
2110 }
2111 
2112 /*!  \reimp
2113 */
operationRemove(Q3NetworkOperation * op)2114 void Q3Ftp::operationRemove( Q3NetworkOperation *op )
2115 {
2116     op->setState( StInProgress );
2117 
2118     cd( ( url()->path().isEmpty() ? QString::fromLatin1("/") : url()->path() ) );
2119     remove( Q3Url( op->arg( 0 ) ).path() );
2120 }
2121 
2122 /*!  \reimp
2123 */
operationRename(Q3NetworkOperation * op)2124 void Q3Ftp::operationRename( Q3NetworkOperation *op )
2125 {
2126     op->setState( StInProgress );
2127 
2128     cd( ( url()->path().isEmpty() ? QString::fromLatin1("/") : url()->path() ) );
2129     rename( op->arg( 0 ), op->arg( 1 ));
2130 }
2131 
2132 /*!  \reimp
2133 */
operationGet(Q3NetworkOperation * op)2134 void Q3Ftp::operationGet( Q3NetworkOperation *op )
2135 {
2136     op->setState( StInProgress );
2137 
2138     Q3Url u( op->arg( 0 ) );
2139     get( u.path() );
2140 }
2141 
2142 /*!  \reimp
2143 */
operationPut(Q3NetworkOperation * op)2144 void Q3Ftp::operationPut( Q3NetworkOperation *op )
2145 {
2146     op->setState( StInProgress );
2147 
2148     Q3Url u( op->arg( 0 ) );
2149     put( op->rawArg(1), u.path() );
2150 }
2151 
2152 /*!  \reimp
2153 */
checkConnection(Q3NetworkOperation * op)2154 bool Q3Ftp::checkConnection( Q3NetworkOperation *op )
2155 {
2156     Q3FtpPrivate *d = dHelper( this );
2157     if ( state() == Unconnected && !d->npWaitForLoginDone ) {
2158 	connect( this, SIGNAL(listInfo(QUrlInfo)),
2159 		this, SLOT(npListInfo(QUrlInfo)) );
2160 	connect( this, SIGNAL(done(bool)),
2161 		this, SLOT(npDone(bool)) );
2162 	connect( this, SIGNAL(stateChanged(int)),
2163 		this, SLOT(npStateChanged(int)) );
2164 	connect( this, SIGNAL(dataTransferProgress(int,int)),
2165 		this, SLOT(npDataTransferProgress(int,int)) );
2166 	connect( this, SIGNAL(readyRead()),
2167 		this, SLOT(npReadyRead()) );
2168 
2169 	d->npWaitForLoginDone = true;
2170 	switch ( op->operation() ) {
2171 	    case OpGet:
2172 	    case OpPut:
2173 		{
2174 		    Q3Url u( op->arg( 0 ) );
2175 		    connectToHost( u.host(), u.port() != -1 ? u.port() : 21 );
2176 		}
2177 		break;
2178 	    default:
2179 		connectToHost( url()->host(), url()->port() != -1 ? url()->port() : 21 );
2180 		break;
2181 	}
2182     QString user = url()->user().isEmpty() ? QString::fromLatin1("anonymous") : url()->user();
2183     QString pass = url()->password().isEmpty() ? QString::fromLatin1("anonymous@") : url()->password();
2184 	login( user, pass );
2185     }
2186 
2187     if ( state() == LoggedIn )
2188 	return true;
2189     return false;
2190 }
2191 
2192 /*!  \reimp
2193 */
supportedOperations() const2194 int Q3Ftp::supportedOperations() const
2195 {
2196     return OpListChildren | OpMkDir | OpRemove | OpRename | OpGet | OpPut;
2197 }
2198 
2199 /*! \internal
2200     Parses the string, \a buffer, which is one line of a directory
2201     listing which came from the FTP server, and sets the values which
2202     have been parsed to the url info object, \a info.
2203 */
parseDir(const QString & buffer,QUrlInfo & info)2204 void Q3Ftp::parseDir( const QString &buffer, QUrlInfo &info )
2205 {
2206     Q3FtpDTP::parseDir( buffer, url()->user(), &info );
2207 }
2208 
npListInfo(const QUrlInfo & i)2209 void Q3Ftp::npListInfo( const QUrlInfo & i )
2210 {
2211     if ( url() ) {
2212 	QRegExp filt( url()->nameFilter(), false, true );
2213 	if ( i.isDir() || filt.search( i.name() ) != -1 ) {
2214 	    emit newChild( i, operationInProgress() );
2215 	}
2216     } else {
2217 	emit newChild( i, operationInProgress() );
2218     }
2219 }
2220 
npDone(bool err)2221 void Q3Ftp::npDone( bool err )
2222 {
2223     Q3FtpPrivate *d = dHelper( this );
2224 
2225     bool emitFinishedSignal = false;
2226     Q3NetworkOperation *op = operationInProgress();
2227     if ( op ) {
2228 	if ( err ) {
2229 	    op->setProtocolDetail( errorString() );
2230 	    op->setState( StFailed );
2231 	    if ( error() == HostNotFound ) {
2232 		op->setErrorCode( (int)ErrHostNotFound );
2233 	    } else {
2234 		switch ( op->operation() ) {
2235 		    case OpListChildren:
2236 			op->setErrorCode( (int)ErrListChildren );
2237 			break;
2238 		    case OpMkDir:
2239 			op->setErrorCode( (int)ErrMkDir );
2240 			break;
2241 		    case OpRemove:
2242 			op->setErrorCode( (int)ErrRemove );
2243 			break;
2244 		    case OpRename:
2245 			op->setErrorCode( (int)ErrRename );
2246 			break;
2247 		    case OpGet:
2248 			op->setErrorCode( (int)ErrGet );
2249 			break;
2250 		    case OpPut:
2251 			op->setErrorCode( (int)ErrPut );
2252 			break;
2253 		}
2254 	    }
2255 	    emitFinishedSignal = true;
2256 	} else if ( !d->npWaitForLoginDone ) {
2257 	    switch ( op->operation() ) {
2258 		case OpRemove:
2259 		    emit removed( op );
2260 		    break;
2261 		case OpMkDir:
2262 		    {
2263 			QUrlInfo inf( op->arg( 0 ), 0, QLatin1String(""), QLatin1String(""), 0, QDateTime(),
2264 				QDateTime(), true, false, false, true, true, true );
2265 			emit newChild( inf, op );
2266 			emit createdDirectory( inf, op );
2267 		    }
2268 		    break;
2269 		case OpRename:
2270 		    emit itemChanged( operationInProgress() );
2271 		    break;
2272 		default:
2273 		    break;
2274 	    }
2275 	    op->setState( StDone );
2276 	    emitFinishedSignal = true;
2277 	}
2278     }
2279     d->npWaitForLoginDone = false;
2280 
2281     if ( state() == Unconnected ) {
2282 	disconnect( this, SIGNAL(listInfo(QUrlInfo)),
2283 		    this, SLOT(npListInfo(QUrlInfo)) );
2284 	disconnect( this, SIGNAL(done(bool)),
2285 		    this, SLOT(npDone(bool)) );
2286 	disconnect( this, SIGNAL(stateChanged(int)),
2287 		    this, SLOT(npStateChanged(int)) );
2288 	disconnect( this, SIGNAL(dataTransferProgress(int,int)),
2289 		    this, SLOT(npDataTransferProgress(int,int)) );
2290 	disconnect( this, SIGNAL(readyRead()),
2291 		    this, SLOT(npReadyRead()) );
2292     }
2293 
2294     // emit the finished() signal at the very end to avoid reentrance problems
2295     if ( emitFinishedSignal )
2296 	emit finished( op );
2297 }
2298 
npStateChanged(int state)2299 void Q3Ftp::npStateChanged( int state )
2300 {
2301     if ( url() ) {
2302 	if ( state == Connecting )
2303 	    emit connectionStateChanged( ConHostFound, QFtp::tr( "Host %1 found" ).arg( url()->host() ) );
2304 	else if ( state == Connected )
2305 	    emit connectionStateChanged( ConConnected, QFtp::tr( "Connected to host %1" ).arg( url()->host() ) );
2306 	else if ( state == Unconnected )
2307 	    emit connectionStateChanged( ConClosed, QFtp::tr( "Connection to %1 closed" ).arg( url()->host() ) );
2308     } else {
2309 	if ( state == Connecting )
2310 	    emit connectionStateChanged( ConHostFound, QFtp::tr( "Host found" ) );
2311 	else if ( state == Connected )
2312 	    emit connectionStateChanged( ConConnected, QFtp::tr( "Connected to host" ) );
2313 	else if ( state == Unconnected )
2314 	    emit connectionStateChanged( ConClosed, QFtp::tr( "Connection closed" ) );
2315     }
2316 }
2317 
npDataTransferProgress(int bDone,int bTotal)2318 void Q3Ftp::npDataTransferProgress( int bDone, int bTotal )
2319 {
2320     emit Q3NetworkProtocol::dataTransferProgress( bDone, bTotal, operationInProgress() );
2321 }
2322 
npReadyRead()2323 void Q3Ftp::npReadyRead()
2324 {
2325     emit data( readAll(), operationInProgress() );
2326 }
2327 
2328 /*!  \internal
2329 */
hostFound()2330 void Q3Ftp::hostFound()
2331 {
2332 }
2333 /*!  \internal
2334 */
connected()2335 void Q3Ftp::connected()
2336 {
2337 }
2338 /*!  \internal
2339 */
closed()2340 void Q3Ftp::closed()
2341 {
2342 }
2343 /*!  \internal
2344 */
dataHostFound()2345 void Q3Ftp::dataHostFound()
2346 {
2347 }
2348 /*!  \internal
2349 */
dataConnected()2350 void Q3Ftp::dataConnected()
2351 {
2352 }
2353 /*!  \internal
2354 */
dataClosed()2355 void Q3Ftp::dataClosed()
2356 {
2357 }
2358 /*!  \internal
2359 */
dataReadyRead()2360 void Q3Ftp::dataReadyRead()
2361 {
2362 }
2363 /*!  \internal
2364 */
dataBytesWritten(int)2365 void Q3Ftp::dataBytesWritten( int )
2366 {
2367 }
2368 /*!  \internal
2369 */
error(int)2370 void Q3Ftp::error( int )
2371 {
2372 }
2373 
2374 QT_END_NAMESPACE
2375 
2376 #include "q3ftp.moc"
2377 
2378 #endif // QT_NO_NETWORKPROTOCOL_FTP
2379