diff options
Diffstat (limited to 'src/qt3support/network')
25 files changed, 17892 insertions, 0 deletions
diff --git a/src/qt3support/network/network.pri b/src/qt3support/network/network.pri new file mode 100644 index 0000000..31ea682 --- /dev/null +++ b/src/qt3support/network/network.pri @@ -0,0 +1,30 @@ +# Qt compat module + +HEADERS += network/q3dns.h \ + network/q3ftp.h \ + network/q3http.h \ + network/q3localfs.h \ + network/q3network.h \ + network/q3networkprotocol.h \ + network/q3socket.h \ + network/q3socketdevice.h \ + network/q3serversocket.h \ + network/q3url.h \ + network/q3urloperator.h + +SOURCES += network/q3dns.cpp \ + network/q3ftp.cpp \ + network/q3http.cpp \ + network/q3localfs.cpp \ + network/q3network.cpp \ + network/q3networkprotocol.cpp \ + network/q3socket.cpp \ + network/q3socketdevice.cpp \ + network/q3serversocket.cpp \ + network/q3url.cpp \ + network/q3urloperator.cpp + +win32:SOURCES += network/q3socketdevice_win.cpp +unix:SOURCES += network/q3socketdevice_unix.cpp +mac:LIBS += -lresolv + diff --git a/src/qt3support/network/q3dns.cpp b/src/qt3support/network/q3dns.cpp new file mode 100644 index 0000000..c53f2ff --- /dev/null +++ b/src/qt3support/network/q3dns.cpp @@ -0,0 +1,2620 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qbytearray.h" +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) || defined(Q_OS_CYGWIN) +# include "qt_windows.h" +#else +# include <sys/types.h> +# include <netinet/in.h> +# include <arpa/nameser.h> +# include <resolv.h> +extern "C" int res_init(); +#endif + +// POSIX Large File Support redefines open -> open64 +#if defined(open) +# undef open +#endif + +// POSIX Large File Support redefines truncate -> truncate64 +#if defined(truncate) +# undef truncate +#endif + +// Solaris redefines connect -> __xnet_connect with _XOPEN_SOURCE_EXTENDED. +#if defined(connect) +# undef connect +#endif + +// UnixWare 7 redefines socket -> _socket +#if defined(socket) +# undef socket +#endif + +#include "q3dns.h" + +#ifndef QT_NO_DNS + +#include "qdatetime.h" +#include "q3dict.h" +#include "q3ptrlist.h" +#include "qstring.h" +#include "qtimer.h" +#include "qapplication.h" +#include "q3ptrvector.h" +#include "q3strlist.h" +#include "q3ptrdict.h" +#include "qfile.h" +#include "qtextstream.h" +#include "q3socketdevice.h" +#include "q3cleanuphandler.h" +#include <limits.h> + +QT_BEGIN_NAMESPACE + +//#define Q3DNS_DEBUG + +static Q_UINT16 theId; // ### seeded started by now() + + +static QDateTime * originOfTime = 0; + +static Q3CleanupHandler<QDateTime> q3dns_cleanup_time; + +static Q_UINT32 now() +{ + if ( originOfTime ) + return originOfTime->secsTo( QDateTime::currentDateTime() ); + + originOfTime = new QDateTime( QDateTime::currentDateTime() ); + theId = originOfTime->time().msec() * 60 + originOfTime->time().second(); + q3dns_cleanup_time.add( &originOfTime ); + return 0; +} + + +static Q3PtrList<QHostAddress> * theNs = 0; +static Q3StrList * theDomains = 0; +static bool ipv6support = false; + +class Q3DnsPrivate { +public: + Q3DnsPrivate() : queryTimer( 0 ), noNames(false) + { +#if defined(Q_DNS_SYNCHRONOUS) +#if defined(Q_OS_UNIX) + noEventLoop = qApp==0 || qApp->loopLevel()==0; +#else + noEventLoop = false; +#endif +#endif + } + ~Q3DnsPrivate() + { + delete queryTimer; + } +private: + QTimer * queryTimer; + bool noNames; +#if defined(Q_DNS_SYNCHRONOUS) + bool noEventLoop; +#endif + + friend class Q3Dns; + friend class Q3DnsAnswer; +}; + + +class Q3DnsRR; +class Q3DnsDomain; + + + +// Q3DnsRR is the class used to store a single RR. Q3DnsRR can store +// all of the supported RR types. a Q3DnsRR is always cached. + +// Q3DnsRR is mostly constructed from the outside. a but hacky, but +// permissible since the entire class is internal. + +class Q3DnsRR { +public: + Q3DnsRR( const QString & label ); + ~Q3DnsRR(); + +public: + Q3DnsDomain * domain; + Q3Dns::RecordType t; + bool nxdomain; + bool current; + Q_UINT32 expireTime; + Q_UINT32 deleteTime; + // somewhat space-wasting per-type data + // a / aaaa + QHostAddress address; + // cname / mx / srv / ptr + QString target; + // mx / srv + Q_UINT16 priority; + // srv + Q_UINT16 weight; + Q_UINT16 port; + // txt + QString text; // could be overloaded into target... +private: + +}; + + +class Q3DnsDomain { +public: + Q3DnsDomain( const QString & label ); + ~Q3DnsDomain(); + + static void add( const QString & label, Q3DnsRR * ); + static Q3PtrList<Q3DnsRR> * cached( const Q3Dns * ); + + void take( Q3DnsRR * ); + + void sweep( Q_UINT32 thisSweep ); + + bool isEmpty() const { return rrs == 0 || rrs->isEmpty(); } + + QString name() const { return l; } + +public: + QString l; + Q3PtrList<Q3DnsRR> * rrs; +}; + + +class Q3DnsQuery: public QTimer { // this inheritance is a very evil hack +public: + Q3DnsQuery(): + id( 0 ), t( Q3Dns::None ), step(0), started(0), + dns( new Q3PtrDict<void>(17) ) {} + ~Q3DnsQuery() { delete dns; } + Q_UINT16 id; + Q3Dns::RecordType t; + QString l; + + uint step; + Q_UINT32 started; + + Q3PtrDict<void> * dns; +}; + + + +class Q3DnsAnswer { +public: + Q3DnsAnswer( Q3DnsQuery * ); + Q3DnsAnswer( const QByteArray &, Q3DnsQuery * ); + ~Q3DnsAnswer(); + + void parse(); + void notify(); + + bool ok; + +private: + Q3DnsQuery * query; + + Q_UINT8 * answer; + int size; + int pp; + + Q3PtrList<Q3DnsRR> * rrs; + + // convenience + int next; + int ttl; + QString label; + Q3DnsRR * rr; + + QString readString(bool multipleLabels = true); + void parseA(); + void parseAaaa(); + void parseMx(); + void parseSrv(); + void parseCname(); + void parsePtr(); + void parseTxt(); + void parseNs(); +}; + + +Q3DnsRR::Q3DnsRR( const QString & label ) + : domain( 0 ), t( Q3Dns::None ), + nxdomain( false ), current( false ), + expireTime( 0 ), deleteTime( 0 ), + priority( 0 ), weight( 0 ), port( 0 ) +{ + Q3DnsDomain::add( label, this ); +} + + +// not supposed to be deleted except by Q3DnsDomain +Q3DnsRR::~Q3DnsRR() +{ + // nothing is necessary +} + + +// this one just sticks in a NXDomain +Q3DnsAnswer::Q3DnsAnswer( Q3DnsQuery * query_ ) +{ + ok = true; + + answer = 0; + size = 0; + query = query_; + pp = 0; + rrs = new Q3PtrList<Q3DnsRR>; + rrs->setAutoDelete( false ); + next = size; + ttl = 0; + label.clear(); + rr = 0; + + Q3DnsRR * newrr = new Q3DnsRR( query->l ); + newrr->t = query->t; + newrr->deleteTime = query->started + 10; + newrr->expireTime = query->started + 10; + newrr->nxdomain = true; + newrr->current = true; + rrs->append( newrr ); +} + + +Q3DnsAnswer::Q3DnsAnswer( const QByteArray& answer_, + Q3DnsQuery * query_ ) +{ + ok = true; + + answer = (Q_UINT8 *)(answer_.data()); + size = (int)answer_.size(); + query = query_; + pp = 0; + rrs = new Q3PtrList<Q3DnsRR>; + rrs->setAutoDelete( false ); + next = size; + ttl = 0; + label.clear(); + rr = 0; +} + + +Q3DnsAnswer::~Q3DnsAnswer() +{ + if ( !ok && rrs ) { + Q3PtrListIterator<Q3DnsRR> it( *rrs ); + Q3DnsRR * tmprr; + while( (tmprr=it.current()) != 0 ) { + ++it; + tmprr->t = Q3Dns::None; // will be deleted soonish + } + } + delete rrs; +} + + +QString Q3DnsAnswer::readString(bool multipleLabels) +{ + int p = pp; + QString r; + Q_UINT8 b; + for( ;; ) { + b = 128; + // Read one character + if ( p >= 0 && p < size ) + b = answer[p]; + + switch( b >> 6 ) { + case 0: + // b is less than 64 + p++; + + // Detect end of data + if ( b == 0 ) { + if ( p > pp ) + pp = p; + return r.isNull() ? QLatin1String( "." ) : r; + } + + // Read a label of size 'b' characters + if ( !r.isNull() ) + r += QLatin1Char('.'); + while( b-- > 0 ) + r += QLatin1Char( answer[p++] ); + + // Return immediately if we were only supposed to read one + // label. + if (!multipleLabels) + return r; + + break; + default: + // Ignore unrecognized control character, or p was out of + // range. + goto not_ok; + case 3: + // Use the next character to determine the relative offset + // to jump to before continuing the packet parsing. + int q = ( (answer[p] & 0x3f) << 8 ) + answer[p+1]; + + if ( q >= pp || q >= p ) + goto not_ok; + if ( p >= pp ) + pp = p + 2; + p = q; + } + } +not_ok: + ok = false; + return QString(); +} + + + +void Q3DnsAnswer::parseA() +{ + if ( next != pp + 4 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %d bytes long IN A for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->t = Q3Dns::A; + rr->address = QHostAddress( ( answer[pp+0] << 24 ) + + ( answer[pp+1] << 16 ) + + ( answer[pp+2] << 8 ) + + ( answer[pp+3] ) ); +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN A %s (ttl %d)", label.ascii(), + rr->address.toString().ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parseAaaa() +{ + if ( next != pp + 16 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %d bytes long IN Aaaa for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->t = Q3Dns::Aaaa; + rr->address = QHostAddress( answer+pp ); +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN Aaaa %s (ttl %d)", label.ascii(), + rr->address.toString().ascii(), ttl ); +#endif +} + + + +void Q3DnsAnswer::parseMx() +{ + if ( next < pp + 2 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %d bytes long IN MX for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->priority = (answer[pp] << 8) + answer[pp+1]; + pp += 2; + rr->target = readString().lower(); + if ( !ok ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw bad string in MX for %s", label.ascii() ); +#endif + return; + } + rr->t = Q3Dns::Mx; +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN MX %d %s (ttl %d)", label.ascii(), + rr->priority, rr->target.ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parseSrv() +{ + if ( next < pp + 6 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %d bytes long IN SRV for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->priority = (answer[pp] << 8) + answer[pp+1]; + rr->weight = (answer[pp+2] << 8) + answer[pp+3]; + rr->port = (answer[pp+4] << 8) + answer[pp+5]; + pp += 6; + rr->target = readString().lower(); + if ( !ok ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw bad string in SRV for %s", label.ascii() ); +#endif + return; + } + rr->t = Q3Dns::Srv; +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN SRV %d %d %d %s (ttl %d)", label.ascii(), + rr->priority, rr->weight, rr->port, rr->target.ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parseCname() +{ + QString target = readString().lower(); + if ( !ok ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw bad cname for for %s", label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->t = Q3Dns::Cname; + rr->target = target; +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN CNAME %s (ttl %d)", label.ascii(), + rr->target.ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parseNs() +{ + QString target = readString().lower(); + if ( !ok ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw bad cname for for %s", label.ascii() ); +#endif + return; + } + + // parse, but ignore + +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN NS %s (ttl %d)", label.ascii(), + target.ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parsePtr() +{ + QString target = readString().lower(); + if ( !ok ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw bad PTR for for %s", label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->t = Q3Dns::Ptr; + rr->target = target; +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN PTR %s (ttl %d)", label.ascii(), + rr->target.ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parseTxt() +{ + QString text = readString(false); + if ( !ok ) { +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw bad TXT for for %s", label.ascii() ); +#endif + return; + } + + rr = new Q3DnsRR( label ); + rr->t = Q3Dns::Txt; + rr->text = text; +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns: saw %s IN TXT \"%s\" (ttl %d)", label.ascii(), + rr->text.ascii(), ttl ); +#endif +} + + +void Q3DnsAnswer::parse() +{ + // okay, do the work... + if ( (answer[2] & 0x78) != 0 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: answer to wrong query type (%d)", answer[1] ); +#endif + ok = false; + return; + } + + // AA + bool aa = (answer[2] & 4) != 0; + + // TC + if ( (answer[2] & 2) != 0 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: truncated answer; pressing on" ); +#endif + } + + // RD + bool rd = (answer[2] & 1) != 0; + + // we don't test RA + // we don't test the MBZ fields + + if ( (answer[3] & 0x0f) == 3 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: saw NXDomain for %s", query->l.ascii() ); +#endif + // NXDomain. cache that for one minute. + rr = new Q3DnsRR( query->l ); + rr->t = query->t; + rr->deleteTime = query->started + 60; + rr->expireTime = query->started + 60; + rr->nxdomain = true; + rr->current = true; + rrs->append( rr ); + return; + } + + if ( (answer[3] & 0x0f) != 0 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: error code %d", answer[3] & 0x0f ); +#endif + ok = false; + return; + } + + int qdcount = ( answer[4] << 8 ) + answer[5]; + int ancount = ( answer[6] << 8 ) + answer[7]; + int nscount = ( answer[8] << 8 ) + answer[9]; + int adcount = (answer[10] << 8 ) +answer[11]; + + pp = 12; + + // read query + while( qdcount > 0 && pp < size ) { + // should I compare the string against query->l? + (void)readString(); + if ( !ok ) + return; + pp += 4; + qdcount--; + } + + // answers and stuff + int rrno = 0; + // if we parse the answer completely, but there are no answers, + // ignore the entire thing. + int answers = 0; + while( ( rrno < ancount || + ( ok && answers >0 && rrno < ancount + nscount + adcount ) ) && + pp < size ) { + label = readString().lower(); + if ( !ok ) + return; + int rdlength = 0; + if ( pp + 10 <= size ) + rdlength = ( answer[pp+8] << 8 ) + answer[pp+9]; + if ( pp + 10 + rdlength > size ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: ran out of stuff to parse (%d+%d>%d (%d)", + pp, rdlength, size, rrno < ancount ); +#endif + // if we're still in the AN section, we should go back and + // at least down the TTLs. probably best to invalidate + // the results. + // the rrs list is good for this + ok = ( rrno < ancount ); + return; + } + uint type, clas; + type = ( answer[pp+0] << 8 ) + answer[pp+1]; + clas = ( answer[pp+2] << 8 ) + answer[pp+3]; + ttl = ( answer[pp+4] << 24 ) + ( answer[pp+5] << 16 ) + + ( answer[pp+6] << 8 ) + answer[pp+7]; + pp = pp + 10; + if ( clas != 1 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: class %d (not internet) for %s", + clas, label.isNull() ? "." : label.ascii() ); +#endif + } else { + next = pp + rdlength; + rr = 0; + switch( type ) { + case 1: + parseA(); + break; + case 28: + parseAaaa(); + break; + case 15: + parseMx(); + break; + case 33: + parseSrv(); + break; + case 5: + parseCname(); + break; + case 12: + parsePtr(); + break; + case 16: + parseTxt(); + break; + case 2: + parseNs(); + break; + default: + // something we don't know +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: type %d for %s", type, + label.isNull() ? "." : label.ascii() ); +#endif + break; + } + if ( rr ) { + rr->deleteTime = 0; + if ( ttl > 0 ) + rr->expireTime = query->started + ttl; + else + rr->expireTime = query->started + 20; + if ( rrno < ancount ) { + answers++; + rr->deleteTime = rr->expireTime; + } + rr->current = true; + rrs->append( rr ); + } + } + if ( !ok ) + return; + pp = next; + next = size; + rrno++; + } + if ( answers == 0 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: answer contained no answers" ); +#endif + ok = ( aa && rd ); + } + + // now go through the list and mark all the As that are referenced + // by something we care about. we want to cache such As. + rrs->first(); + Q3Dict<void> used( 17 ); + used.setAutoDelete( false ); + while( (rr=rrs->current()) != 0 ) { + rrs->next(); + if ( rr->target.length() && rr->deleteTime > 0 && rr->current ) + used.insert( rr->target, (void*)42 ); + if ( ( rr->t == Q3Dns::A || rr->t == Q3Dns::Aaaa ) && + used.find( rr->domain->name() ) != 0 ) + rr->deleteTime = rr->expireTime; + } + + // next, for each RR, delete any older RRs that are equal to it + rrs->first(); + while( (rr=rrs->current()) != 0 ) { + rrs->next(); + if ( rr && rr->domain && rr->domain->rrs ) { + Q3PtrList<Q3DnsRR> * drrs = rr->domain->rrs; + drrs->first(); + Q3DnsRR * older; + while( (older=drrs->current()) != 0 ) { + if ( older != rr && + older->t == rr->t && + older->nxdomain == rr->nxdomain && + older->address == rr->address && + older->target == rr->target && + older->priority == rr->priority && + older->weight == rr->weight && + older->port == rr->port && + older->text == rr->text ) { + // well, it's equal, but it's not the same. so we kill it, + // but use its expiry time. +#if defined(Q3DNS_DEBUG) + qDebug( "killing off old %d for %s, expire was %d", + older->t, older->domain->name().latin1(), + rr->expireTime ); +#endif + older->t = Q3Dns::None; + rr->expireTime = QMAX( older->expireTime, rr->expireTime ); + rr->deleteTime = QMAX( older->deleteTime, rr->deleteTime ); + older->deleteTime = 0; +#if defined(Q3DNS_DEBUG) + qDebug( " adjusted expire is %d", rr->expireTime ); +#endif + } + drrs->next(); + } + } + } + +#if defined(Q3DNS_DEBUG) + //qDebug( "DNS Manager: ()" ); +#endif +} + + +class Q3DnsUgleHack: public Q3Dns { +public: + void ugle( bool emitAnyway=false ); +}; + + +void Q3DnsAnswer::notify() +{ + if ( !rrs || !ok || !query || !query->dns ) + return; + + Q3PtrDict<void> notified; + notified.setAutoDelete( false ); + + Q3PtrDictIterator<void> it( *query->dns ); + Q3Dns * dns; + it.toFirst(); + while( (dns=(Q3Dns*)(it.current())) != 0 ) { + ++it; + if ( notified.find( (void*)dns ) == 0 ) { + notified.insert( (void*)dns, (void*)42 ); + if ( rrs->count() == 0 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: found no answers!" ); +#endif + dns->d->noNames = true; + ((Q3DnsUgleHack*)dns)->ugle( true ); + } else { + QStringList n = dns->qualifiedNames(); + if ( query && n.contains(query->l) ) + ((Q3DnsUgleHack*)dns)->ugle(); +#if defined(Q3DNS_DEBUG) + else + qDebug( "DNS Manager: DNS thing %s not notified for %s", + dns->label().ascii(), query->l.ascii() ); +#endif + } + } + } +} + + +// +// +// Q3DnsManager +// +// + + +class Q3DnsManager: public Q3DnsSocket { +private: +public: // just to silence the moronic g++. + Q3DnsManager(); + ~Q3DnsManager(); +public: + static Q3DnsManager * manager(); + + Q3DnsDomain * domain( const QString & ); + + void transmitQuery( Q3DnsQuery * ); + void transmitQuery( int ); + + // reimplementation of the slots + void cleanCache(); + void retransmit(); + void answer(); + +public: + Q3PtrVector<Q3DnsQuery> queries; + Q3Dict<Q3DnsDomain> cache; + Q3SocketDevice * ipv4Socket; +#if !defined (QT_NO_IPV6) + Q3SocketDevice * ipv6Socket; +#endif +}; + + + +static Q3DnsManager * globalManager = 0; + +static void cleanupDns() +{ + delete globalManager; + globalManager = 0; +} + +Q3DnsManager * Q3DnsManager::manager() +{ + if ( !globalManager ) { + qAddPostRoutine(cleanupDns); + new Q3DnsManager(); + } + return globalManager; +} + + +void Q3DnsUgleHack::ugle( bool emitAnyway) +{ + if ( emitAnyway || !isWorking() ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: status change for %s (type %d)", + label().ascii(), recordType() ); +#endif + emit resultsReady(); + } +} + + +Q3DnsManager::Q3DnsManager() + : Q3DnsSocket( qApp, "Internal DNS manager" ), + queries( Q3PtrVector<Q3DnsQuery>( 0 ) ), + cache( Q3Dict<Q3DnsDomain>( 83, false ) ), + ipv4Socket( new Q3SocketDevice( Q3SocketDevice::Datagram, Q3SocketDevice::IPv4, 0 ) ) +#if !defined (QT_NO_IPV6) + , ipv6Socket( new Q3SocketDevice( Q3SocketDevice::Datagram, Q3SocketDevice::IPv6, 0 ) ) +#endif +{ + cache.setAutoDelete( true ); + globalManager = this; + + QTimer * sweepTimer = new QTimer( this ); + sweepTimer->start( 1000 * 60 * 3 ); + connect( sweepTimer, SIGNAL(timeout()), + this, SLOT(cleanCache()) ); + + QSocketNotifier * rn4 = new QSocketNotifier( ipv4Socket->socket(), + QSocketNotifier::Read, + this, "dns IPv4 socket watcher" ); + ipv4Socket->setAddressReusable( false ); + ipv4Socket->setBlocking( false ); + connect( rn4, SIGNAL(activated(int)), SLOT(answer()) ); + +#if !defined (QT_NO_IPV6) + // Don't connect the IPv6 socket notifier if the host does not + // support IPv6. + if ( ipv6Socket->socket() != -1 ) { + QSocketNotifier * rn6 = new QSocketNotifier( ipv6Socket->socket(), + QSocketNotifier::Read, + this, "dns IPv6 socket watcher" ); + + ipv6support = true; + ipv6Socket->setAddressReusable( false ); + ipv6Socket->setBlocking( false ); + connect( rn6, SIGNAL(activated(int)), SLOT(answer()) ); + } +#endif + + if ( !theNs ) + Q3Dns::doResInit(); + + // O(n*n) stuff here. but for 3 and 6, O(n*n) with a low k should + // be perfect. the point is to eliminate any duplicates that + // might be hidden in the lists. + Q3PtrList<QHostAddress> * ns = new Q3PtrList<QHostAddress>; + + theNs->first(); + QHostAddress * h; + while( (h=theNs->current()) != 0 ) { + ns->first(); + while( ns->current() != 0 && !(*ns->current() == *h) ) + ns->next(); + if ( !ns->current() ) { + ns->append( new QHostAddress(*h) ); +#if defined(Q3DNS_DEBUG) + qDebug( "using name server %s", h->toString().latin1() ); + } else { + qDebug( "skipping address %s", h->toString().latin1() ); +#endif + } + theNs->next(); + } + + delete theNs; + theNs = ns; + theNs->setAutoDelete( true ); + + Q3StrList * domains = new Q3StrList( true ); + + theDomains->first(); + const char * s; + while( (s=theDomains->current()) != 0 ) { + domains->first(); + while( domains->current() != 0 && qstrcmp( domains->current(), s ) ) + domains->next(); + if ( !domains->current() ) { + domains->append( s ); +#if defined(Q3DNS_DEBUG) + qDebug( "searching domain %s", s ); + } else { + qDebug( "skipping domain %s", s ); +#endif + } + theDomains->next(); + } + + delete theDomains; + theDomains = domains; + theDomains->setAutoDelete( true ); +} + + +Q3DnsManager::~Q3DnsManager() +{ + if ( globalManager ) + globalManager = 0; + queries.setAutoDelete( true ); + cache.setAutoDelete( true ); + delete ipv4Socket; +#if !defined (QT_NO_IPV6) + delete ipv6Socket; +#endif +} + +static Q_UINT32 lastSweep = 0; + +void Q3DnsManager::cleanCache() +{ + bool again = false; + Q3DictIterator<Q3DnsDomain> it( cache ); + Q3DnsDomain * d; + Q_UINT32 thisSweep = now(); +#if defined(Q3DNS_DEBUG) + qDebug( "Q3DnsManager::cleanCache(: Called, time is %u, last was %u", + thisSweep, lastSweep ); +#endif + + while( (d=it.current()) != 0 ) { + ++it; + d->sweep( thisSweep ); // after this, d may be empty + if ( !again ) + again = !d->isEmpty(); + } + if ( !again ) + delete this; + lastSweep = thisSweep; +} + + +void Q3DnsManager::retransmit() +{ + const QObject * o = sender(); + if ( o == 0 || globalManager == 0 || this != globalManager ) + return; + uint q = 0; + while( q < queries.size() && queries[q] != o ) + q++; + if ( q < queries.size() ) + transmitQuery( q ); +} + + +void Q3DnsManager::answer() +{ + QByteArray a( 16383 ); // large enough for anything, one suspects + + int r; +#if defined (QT_NO_IPV6) + r = ipv4Socket->readBlock(a.data(), a.size()); +#else + if (((QSocketNotifier *)sender())->socket() == ipv4Socket->socket()) + r = ipv4Socket->readBlock(a.data(), a.size()); + else + r = ipv6Socket->readBlock(a.data(), a.size()); +#endif +#if defined(Q3DNS_DEBUG) +#if !defined (QT_NO_IPV6) + qDebug("DNS Manager: answer arrived: %d bytes from %s:%d", r, + useIpv4Socket ? ipv4Socket->peerAddress().toString().ascii() + : ipv6Socket->peerAddress().toString().ascii(), + useIpv4Socket ? ipv4Socket->peerPort() : ipv6Socket->peerPort() ); +#else + qDebug("DNS Manager: answer arrived: %d bytes from %s:%d", r, + ipv4Socket->peerAddress().toString().ascii(), ipv4Socket->peerPort());; +#endif +#endif + if ( r < 12 ) + return; + // maybe we should check that the answer comes from port 53 on one + // of our name servers... + a.resize( r ); + + Q_UINT16 aid = (((Q_UINT8)a[0]) << 8) + ((Q_UINT8)a[1]); + uint i = 0; + while( i < queries.size() && + !( queries[i] && queries[i]->id == aid ) ) + i++; + if ( i == queries.size() ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: bad id (0x%04x) %d", aid, i ); +#endif + return; + } + + // at this point queries[i] is whatever we asked for. + + if ( ( (Q_UINT8)(a[2]) & 0x80 ) == 0 ) { +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: received a query" ); +#endif + return; + } + + Q3DnsQuery * q = queries[i]; + Q3DnsAnswer answer( a, q ); + answer.parse(); + if ( answer.ok ) { + queries.take( i ); + answer.notify(); + delete q; + } +} + + +void Q3DnsManager::transmitQuery( Q3DnsQuery * query_ ) +{ + if ( !query_ ) + return; + + uint i = 0; + while( i < queries.size() && queries[i] != 0 ) + i++; + if ( i == queries.size() ) + queries.resize( i+1 ); + queries.insert( i, query_ ); + transmitQuery( i ); +} + + +void Q3DnsManager::transmitQuery( int i ) +{ + if ( i < 0 || i >= (int)queries.size() ) + return; + Q3DnsQuery * q = queries[i]; + + if ( q && q->step > 8 ) { + // okay, we've run out of retransmissions. we fake an NXDomain + // with a very short life time... + Q3DnsAnswer answer( q ); + answer.notify(); + // and then get rid of the query + queries.take( i ); +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: giving up on query 0x%04x", q->id ); +#endif + delete q; + QTimer::singleShot( 0, Q3DnsManager::manager(), SLOT(cleanCache()) ); + // and don't process anything more + return; + } + + if ((q && !q->dns) || q->dns->isEmpty()) + // no one currently wants the answer, so there's no point in + // retransmitting the query. we keep it, though. an answer may + // arrive for an earlier query transmission, and if it does we + // may benefit from caching the result. + return; + + QByteArray p( 12 + q->l.length() + 2 + 4 ); + if ( p.size() > 500 ) + return; // way over the limit, so don't even try + + // header + // id + p[0] = (q->id & 0xff00) >> 8; + p[1] = q->id & 0x00ff; + p[2] = 1; // recursion desired, rest is 0 + p[3] = 0; // all is 0 + // one query + p[4] = 0; + p[5] = 1; + // no answers, name servers or additional data + p[6] = p[7] = p[8] = p[9] = p[10] = p[11] = 0; + + // the name is composed of several components. each needs to be + // written by itself... so we write... + // oh, and we assume that there's no funky characters in there. + int pp = 12; + uint lp = 0; + while( lp < (uint) q->l.length() ) { + int le = q->l.find( QLatin1Char('.'), lp ); + if ( le < 0 ) + le = q->l.length(); + QString component = q->l.mid( lp, le-lp ); + p[pp++] = component.length(); + int cp; + for( cp=0; cp < (int)component.length(); cp++ ) + p[pp++] = component[cp].latin1(); + lp = le + 1; + } + // final null + p[pp++] = 0; + // query type + p[pp++] = 0; + switch( q->t ) { + case Q3Dns::A: + p[pp++] = 1; + break; + case Q3Dns::Aaaa: + p[pp++] = 28; + break; + case Q3Dns::Mx: + p[pp++] = 15; + break; + case Q3Dns::Srv: + p[pp++] = 33; + break; + case Q3Dns::Cname: + p[pp++] = 5; + break; + case Q3Dns::Ptr: + p[pp++] = 12; + break; + case Q3Dns::Txt: + p[pp++] = 16; + break; + default: + p[pp++] = (char)255; // any + break; + } + // query class (always internet) + p[pp++] = 0; + p[pp++] = 1; + + // if we have no name servers, we should regenerate ns in case + // name servers have recently been defined (like on windows, + // plugging/unplugging the network cable will change the name + // server entries) + if ( !theNs || theNs->isEmpty() ) + Q3Dns::doResInit(); + + if ( !theNs || theNs->isEmpty() ) { + // we don't find any name servers. We fake an NXDomain + // with a very short life time... + Q3DnsAnswer answer( q ); + answer.notify(); + // and then get rid of the query + queries.take( i ); +#if defined(Q3DNS_DEBUG) + qDebug( "DNS Manager: no DNS server found on query 0x%04x", q->id ); +#endif + delete q; + QTimer::singleShot( 1000*10, Q3DnsManager::manager(), SLOT(cleanCache()) ); + // and don't process anything more + return; + } + + QHostAddress receiver = *theNs->at( q->step % theNs->count() ); + if (receiver.isIPv4Address()) + ipv4Socket->writeBlock( p.data(), pp, receiver, 53 ); +#if !defined (QT_NO_IPV6) + else + ipv6Socket->writeBlock( p.data(), pp, receiver, 53 ); +#endif +#if defined(Q3DNS_DEBUG) + qDebug( "issuing query 0x%04x (%d) about %s type %d to %s", + q->id, q->step, q->l.ascii(), q->t, + ns->at( q->step % ns->count() )->toString().ascii() ); +#endif + if ( theNs->count() > 1 && q->step == 0 && queries.count() == 1 ) { + // if it's the first time, and we don't have any other + // outstanding queries, send nonrecursive queries to the other + // name servers too. + p[2] = 0; + QHostAddress * server; + while( (server=theNs->next()) != 0 ) { + if (server->isIPv4Address()) + ipv4Socket->writeBlock( p.data(), pp, *server, 53 ); +#if !defined (QT_NO_IPV6) + else + ipv6Socket->writeBlock( p.data(), pp, *server, 53 ); +#endif +#if defined(Q3DNS_DEBUG) + qDebug( "copying query to %s", server->toString().ascii() ); +#endif + } + } + q->step++; + // some testing indicates that normal dns queries take up to 0.6 + // seconds. the graph becomes steep around that point, and the + // number of errors rises... so it seems good to retry at that + // point. + q->start( q->step < theNs->count() ? 800 : 1500, true ); +} + + +Q3DnsDomain * Q3DnsManager::domain( const QString & label ) +{ + Q3DnsDomain * d = cache.find( label ); + if ( !d ) { + d = new Q3DnsDomain( label ); + cache.insert( label, d ); + } + return d; +} + + +// +// +// the Q3DnsDomain class looks after and coordinates queries for Q3DnsRRs for +// each domain, and the cached Q3DnsRRs. (A domain, in DNS terminology, is +// a node in the DNS. "no", "trolltech.com" and "lupinella.troll.no" are +// all domains.) +// +// + + +Q3DnsDomain::Q3DnsDomain( const QString & label ) +{ + l = label; + rrs = 0; +} + + +Q3DnsDomain::~Q3DnsDomain() +{ + delete rrs; + rrs = 0; +} + + +void Q3DnsDomain::add( const QString & label, Q3DnsRR * rr ) +{ + Q3DnsDomain * d = Q3DnsManager::manager()->domain( label ); + if ( !d->rrs ) { + d->rrs = new Q3PtrList<Q3DnsRR>; + d->rrs->setAutoDelete( true ); + } + d->rrs->append( rr ); + rr->domain = d; +} + + +Q3PtrList<Q3DnsRR> * Q3DnsDomain::cached( const Q3Dns * r ) +{ + Q3PtrList<Q3DnsRR> * l = new Q3PtrList<Q3DnsRR>; + + // test at first if you have to start a query at all + if ( r->recordType() == Q3Dns::A ) { + if ( r->label().lower() == QLatin1String("localhost") ) { + // undocumented hack. ipv4-specific. also, may be a memory + // leak? not sure. would be better to do this in doResInit(), + // anyway. + Q3DnsRR *rrTmp = new Q3DnsRR( r->label() ); + rrTmp->t = Q3Dns::A; + rrTmp->address = QHostAddress( 0x7f000001 ); + rrTmp->current = true; + l->append( rrTmp ); + return l; + } + QHostAddress tmp; + if ( tmp.setAddress( r->label() ) ) { + Q3DnsRR *rrTmp = new Q3DnsRR( r->label() ); + if ( tmp.isIPv4Address() ) { + rrTmp->t = Q3Dns::A; + rrTmp->address = tmp; + rrTmp->current = true; + l->append( rrTmp ); + } else { + rrTmp->nxdomain = true; + } + return l; + } + } + if ( r->recordType() == Q3Dns::Aaaa ) { + QHostAddress tmp; + if ( tmp.setAddress(r->label()) ) { + Q3DnsRR *rrTmp = new Q3DnsRR( r->label() ); + if ( tmp.isIPv6Address() ) { + rrTmp->t = Q3Dns::Aaaa; + rrTmp->address = tmp; + rrTmp->current = true; + l->append( rrTmp ); + } else { + rrTmp->nxdomain = true; + } + return l; + } + } + + // if you reach this point, you have to do the query + Q3DnsManager * m = Q3DnsManager::manager(); + QStringList n = r->qualifiedNames(); + bool nxdomain; + int cnamecount = 0; + int it = 0; + while( it < n.count() ) { + QString s = n.at(it++); + nxdomain = false; +#if defined(Q3DNS_DEBUG) + qDebug( "looking at cache for %s (%s %d)", + s.ascii(), r->label().ascii(), r->recordType() ); +#endif + Q3DnsDomain * d = m->domain( s ); +#if defined(Q3DNS_DEBUG) + qDebug( " - found %d RRs", d && d->rrs ? d->rrs->count() : 0 ); +#endif + if ( d->rrs ) + d->rrs->first(); + Q3DnsRR * rr; + bool answer = false; + while( d->rrs && (rr=d->rrs->current()) != 0 ) { + if ( rr->t == Q3Dns::Cname && r->recordType() != Q3Dns::Cname && + !rr->nxdomain && cnamecount < 16 ) { + // cname. if the code is ugly, that may just + // possibly be because the concept is. +#if defined(Q3DNS_DEBUG) + qDebug( "found cname from %s to %s", + r->label().ascii(), rr->target.ascii() ); +#endif + s = rr->target; + d = m->domain( s ); + if ( d->rrs ) + d->rrs->first(); + it = n.count(); + // we've elegantly moved over to whatever the cname + // pointed to. well, not elegantly. let's remember + // that we've done something, anyway, so we can't be + // fooled into an infinte loop as well. + cnamecount++; + } else { + if ( rr->t == r->recordType() ) { + if ( rr->nxdomain ) + nxdomain = true; + else + answer = true; + l->append( rr ); + if ( rr->deleteTime <= lastSweep ) { + // we're returning something that'll be + // deleted soon. we assume that if the client + // wanted it twice, it'll want it again, so we + // ask the name server again right now. + Q3DnsQuery * query = new Q3DnsQuery; + query->started = now(); + query->id = ++theId; + query->t = rr->t; + query->l = rr->domain->name(); + // note that here, we don't bother about + // notification. but we do bother about + // timeouts: we make sure to use high timeouts + // and few tramsissions. + query->step = theNs->count(); + QObject::connect( query, SIGNAL(timeout()), + Q3DnsManager::manager(), + SLOT(retransmit()) ); + Q3DnsManager::manager()->transmitQuery( query ); + } + } + d->rrs->next(); + } + } + // if we found a positive result, return quickly + if ( answer && l->count() ) { +#if defined(Q3DNS_DEBUG) + qDebug( "found %d records for %s", + l->count(), r->label().ascii() ); + l->first(); + while( l->current() ) { + qDebug( " type %d target %s address %s", + l->current()->t, + l->current()->target.latin1(), + l->current()->address.toString().latin1() ); + l->next(); + } +#endif + l->first(); + return l; + } + +#if defined(Q3DNS_DEBUG) + if ( nxdomain ) + qDebug( "found NXDomain %s", s.ascii() ); +#endif + + if ( !nxdomain ) { + // if we didn't, and not a negative result either, perhaps + // we need to transmit a query. + uint q = 0; + while ( q < m->queries.size() && + ( m->queries[q] == 0 || + m->queries[q]->t != r->recordType() || + m->queries[q]->l != s ) ) + q++; + // we haven't done it before, so maybe we should. but + // wait - if it's an unqualified name, only ask when all + // the other alternatives are exhausted. + if ( q == m->queries.size() && ( s.find( QLatin1Char('.') ) >= 0 || + int(l->count()) >= n.count()-1 ) ) { + Q3DnsQuery * query = new Q3DnsQuery; + query->started = now(); + query->id = ++theId; + query->t = r->recordType(); + query->l = s; + query->dns->replace( (void*)r, (void*)r ); + QObject::connect( query, SIGNAL(timeout()), + Q3DnsManager::manager(), SLOT(retransmit()) ); + Q3DnsManager::manager()->transmitQuery( query ); + } else if ( q < m->queries.size() ) { + // if we've found an earlier query for the same + // domain/type, subscribe to its answer + m->queries[q]->dns->replace( (void*)r, (void*)r ); + } + } + } + l->first(); + return l; +} + + +void Q3DnsDomain::sweep( Q_UINT32 thisSweep ) +{ + if ( !rrs ) + return; + + Q3DnsRR * rr; + rrs->first(); + while( (rr=rrs->current()) != 0 ) { + if ( !rr->deleteTime ) + rr->deleteTime = thisSweep; // will hit next time around + +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::sweep: %s type %d expires %u %u - %s / %s", + rr->domain->name().latin1(), rr->t, + rr->expireTime, rr->deleteTime, + rr->target.latin1(), rr->address.toString().latin1()); +#endif + if ( rr->current == false || + rr->t == Q3Dns::None || + rr->deleteTime <= thisSweep || + rr->expireTime <= thisSweep ) + rrs->remove(); + else + rrs->next(); + } + + if ( rrs->isEmpty() ) { + delete rrs; + rrs = 0; + } +} + + + + +// the itsy-bitsy little socket class I don't really need except for +// so I can subclass and reimplement the slots. + + +Q3DnsSocket::Q3DnsSocket( QObject * parent, const char * name ) + : QObject( parent, name ) +{ + // nothing +} + + +Q3DnsSocket::~Q3DnsSocket() +{ + // nothing +} + + +void Q3DnsSocket::cleanCache() +{ + // nothing +} + + +void Q3DnsSocket::retransmit() +{ + // nothing +} + + +void Q3DnsSocket::answer() +{ + // nothing +} + + +/*! + \class Q3Dns + \brief The Q3Dns class provides asynchronous DNS lookups. + + \compat + + Both Windows and Unix provide synchronous DNS lookups; Windows + provides some asynchronous support too. At the time of writing + neither operating system provides asynchronous support for + anything other than hostname-to-address mapping. + + Q3Dns rectifies this shortcoming, by providing asynchronous caching + lookups for the record types that we expect modern GUI + applications to need in the near future. + + The class is \e not straightforward to use (although it is much + simpler than the native APIs); Q3Socket provides much easier to use + TCP connection facilities. The aim of Q3Dns is to provide a correct + and small API to the DNS and nothing more. (We use "correctness" + to mean that the DNS information is correctly cached, and + correctly timed out.) + + The API comprises a constructor, functions to set the DNS node + (the domain in DNS terminology) and record type (setLabel() and + setRecordType()), the corresponding get functions, an isWorking() + function to determine whether Q3Dns is working or reading, a + resultsReady() signal and query functions for the result. + + There is one query function for each RecordType, namely + addresses(), mailServers(), servers(), hostNames() and texts(). + There are also two generic query functions: canonicalName() + returns the name you'll presumably end up using (the exact meaning + of this depends on the record type) and qualifiedNames() returns a + list of the fully qualified names label() maps to. + + \sa Q3Socket +*/ + +/*! + Constructs a DNS query object with invalid settings for both the + label and the search type. +*/ + +Q3Dns::Q3Dns() +{ + d = new Q3DnsPrivate; + t = None; +} + + + + +/*! + Constructs a DNS query object that will return record type \a rr + information about \a label. + + The DNS lookup is started the next time the application enters the + event loop. When the result is found the signal resultsReady() is + emitted. + + \a rr defaults to \c A, IPv4 addresses. +*/ + +Q3Dns::Q3Dns( const QString & label, RecordType rr ) +{ + d = new Q3DnsPrivate; + t = rr; + setLabel( label ); + setStartQueryTimer(); // start query the next time we enter event loop +} + + + +/*! + Constructs a DNS query object that will return record type \a rr + information about host address \a address. The label is set to the + IN-ADDR.ARPA domain name. This is useful in combination with the + \c Ptr record type (e.g. if you want to look up a hostname for a + given address). + + The DNS lookup is started the next time the application enters the + event loop. When the result is found the signal resultsReady() is + emitted. + + \a rr defaults to \c Ptr, that maps addresses to hostnames. +*/ + +Q3Dns::Q3Dns( const QHostAddress & address, RecordType rr ) +{ + d = new Q3DnsPrivate; + t = rr; + setLabel( address ); + setStartQueryTimer(); // start query the next time we enter event loop +} + + + + +/*! + Destroys the DNS query object and frees its allocated resources. +*/ + +Q3Dns::~Q3Dns() +{ + if ( globalManager ) { + uint q = 0; + Q3DnsManager * m = globalManager; + while( q < m->queries.size() ) { + Q3DnsQuery * query=m->queries[q]; + if ( query && query->dns ) + (void)query->dns->take( (void*) this ); + q++; + } + + } + + delete d; + d = 0; +} + + + + +/*! + Sets this DNS query object to query for information about \a + label. + + This does not change the recordType(), but its isWorking() status + will probably change as a result. + + The DNS lookup is started the next time the application enters the + event loop. When the result is found the signal resultsReady() is + emitted. +*/ + +void Q3Dns::setLabel( const QString & label ) +{ + l = label; + d->noNames = false; + + // construct a list of qualified names + n.clear(); + if ( l.length() > 1 && l[(int)l.length()-1] == QLatin1Char('.') ) { + n.append( l.left( l.length()-1 ).lower() ); + } else { + int i = l.length(); + int dots = 0; + const int maxDots = 2; + while( i && dots < maxDots ) { + if ( l[--i] == QLatin1Char('.') ) + dots++; + } + if ( dots < maxDots ) { + (void)Q3DnsManager::manager(); // create a Q3DnsManager, if it is not already there + Q3StrListIterator it( *theDomains ); + const char * dom; + while( (dom=it.current()) != 0 ) { + ++it; + n.append( l.lower() + QLatin1String(".") + QLatin1String(dom) ); + } + } + n.append( l.lower() ); + } + +#if defined(Q_DNS_SYNCHRONOUS) + if ( d->noEventLoop ) { + doSynchronousLookup(); + } else { + setStartQueryTimer(); // start query the next time we enter event loop + } +#else + setStartQueryTimer(); // start query the next time we enter event loop +#endif +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::setLabel: %d address(es) for %s", n.count(), l.ascii() ); + int i = 0; + for( i = 0; i < (int)n.count(); i++ ) + qDebug( "Q3Dns::setLabel: %d: %s", i, n[i].ascii() ); +#endif +} + + +/*! + \overload + + Sets this DNS query object to query for information about the host + address \a address. The label is set to the IN-ADDR.ARPA domain + name. This is useful in combination with the \c Ptr record type + (e.g. if you want to look up a hostname for a given address). +*/ + +void Q3Dns::setLabel( const QHostAddress & address ) +{ + setLabel( toInAddrArpaDomain( address ) ); +} + + +/*! + \fn QStringList Q3Dns::qualifiedNames() const + + Returns a list of the fully qualified names label() maps to. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3dns.cpp 0 + +*/ + + +/*! + \fn QString Q3Dns::label() const + + Returns the domain name for which this object returns information. + + \sa setLabel() +*/ + +/*! + \enum Q3Dns::RecordType + + This enum type defines the record types Q3Dns can handle. The DNS + provides many more; these are the ones we've judged to be in + current use, useful for GUI programs and important enough to + support right away: + + \value None No information. This exists only so that Q3Dns can + have a default. + + \value A IPv4 addresses. By far the most common type. + + \value Aaaa IPv6 addresses. So far mostly unused. + + \value Mx Mail eXchanger names. Used for mail delivery. + + \value Srv SeRVer names. Generic record type for finding + servers. So far mostly unused. + + \value Cname Canonical names. Maps from nicknames to the true + name (the canonical name) for a host. + + \value Ptr name PoinTeRs. Maps from IPv4 or IPv6 addresses to hostnames. + + \value Txt arbitrary TeXT for domains. + + We expect that some support for the + \l{http://www.rfc-editor.org/rfc/rfc2535.txt}{RFC 2535} + extensions will be added in future versions. +*/ + +/*! + Sets this object to query for record type \a rr records. + + The DNS lookup is started the next time the application enters the + event loop. When the result is found the signal resultsReady() is + emitted. + + \sa RecordType +*/ + +void Q3Dns::setRecordType( RecordType rr ) +{ + t = rr; + d->noNames = false; + setStartQueryTimer(); // start query the next time we enter event loop +} + +/*! + \internal + + Private slot for starting the query. +*/ +void Q3Dns::startQuery() +{ + // isWorking() starts the query (if necessary) + if ( !isWorking() ) + emit resultsReady(); +} + +/*! + The three functions Q3Dns::Q3Dns(QString, RecordType), + Q3Dns::setLabel() and Q3Dns::setRecordType() may start a DNS lookup. + This function handles setting up the single shot timer. +*/ +void Q3Dns::setStartQueryTimer() +{ +#if defined(Q_DNS_SYNCHRONOUS) + if ( !d->queryTimer && !d->noEventLoop ) +#else + if ( !d->queryTimer ) +#endif + { + // start the query the next time we enter event loop + d->queryTimer = new QTimer( this ); + connect( d->queryTimer, SIGNAL(timeout()), + this, SLOT(startQuery()) ); + d->queryTimer->start( 0, true ); + } +} + +/* + Transforms the host address \a address to the IN-ADDR.ARPA domain + name. Returns something indeterminate if you're sloppy or + naughty. This function has an IPv4-specific name, but works for + IPv6 too. +*/ +QString Q3Dns::toInAddrArpaDomain( const QHostAddress &address ) +{ + QString s; + if ( address.isNull() ) { + // if the address isn't valid, neither of the other two make + // cases make sense. better to just return. + } else if ( address.isIp4Addr() ) { + Q_UINT32 i = address.ip4Addr(); + s.sprintf( "%d.%d.%d.%d.IN-ADDR.ARPA", + i & 0xff, (i >> 8) & 0xff, (i>>16) & 0xff, (i>>24) & 0xff ); + } else { + // RFC 3152. (1886 is deprecated, and clients no longer need to + // support it, in practice). + Q_IPV6ADDR i = address.toIPv6Address(); + s = QLatin1String("ip6.arpa"); + uint b = 0; + while( b < 16 ) { + s = QString::number( i.c[b]%16, 16 ) + QLatin1String(".") + + QString::number( i.c[b]/16, 16 ) + QLatin1String(".") + s; + b++; + } + } + return s; +} + + +/*! + \fn Q3Dns::RecordType Q3Dns::recordType() const + + Returns the record type of this DNS query object. + + \sa setRecordType() RecordType +*/ + +/*! + \fn void Q3Dns::resultsReady() + + This signal is emitted when results are available for one of the + qualifiedNames(). +*/ + +/*! + Returns true if Q3Dns is doing a lookup for this object (i.e. if it + does not already have the necessary information); otherwise + returns false. + + Q3Dns emits the resultsReady() signal when the status changes to false. +*/ + +bool Q3Dns::isWorking() const +{ +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::isWorking (%s, %d)", l.ascii(), t ); +#endif + if ( t == None ) + return false; + +#if defined(Q_DNS_SYNCHRONOUS) + if ( d->noEventLoop ) + return true; +#endif + + Q3PtrList<Q3DnsRR> * ll = Q3DnsDomain::cached( this ); + Q_LONG queries = n.count(); + while( ll->current() != 0 ) { + if ( ll->current()->nxdomain ) { + queries--; + } else { + delete ll; + return false; + } + ll->next(); + } + delete ll; + + if ( queries <= 0 ) + return false; + if ( d->noNames ) + return false; + return true; +} + + +/*! + Returns a list of the addresses for this name if this Q3Dns object + has a recordType() of Q3Dns::A or Q3Dns::Aaaa and the answer + is available; otherwise returns an empty list. + + As a special case, if label() is a valid numeric IP address, this + function returns that address. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3dns.cpp 1 + +*/ + +Q3ValueList<QHostAddress> Q3Dns::addresses() const +{ +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::addresses (%s)", l.ascii() ); +#endif + Q3ValueList<QHostAddress> result; + if ( t != A && t != Aaaa ) + return result; + + Q3PtrList<Q3DnsRR> * cached = Q3DnsDomain::cached( this ); + + Q3DnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) + result.append( rr->address ); + cached->next(); + } + delete cached; + return result; +} + + +/*! + \class Q3Dns::MailServer + \brief The Q3Dns::MailServer class is described in Q3Dns::mailServers(). + + \internal +*/ + +/*! + Returns a list of mail servers if the record type is \c Mx. The + class Q3Dns::MailServer contains the following public variables: + \list + \i QString Q3Dns::MailServer::name + \i Q_UINT16 Q3Dns::MailServer::priority + \endlist + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3dns.cpp 2 + +*/ +Q3ValueList<Q3Dns::MailServer> Q3Dns::mailServers() const +{ +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::mailServers (%s)", l.ascii() ); +#endif + Q3ValueList<Q3Dns::MailServer> result; + if ( t != Mx ) + return result; + + Q3PtrList<Q3DnsRR> * cached = Q3DnsDomain::cached( this ); + + Q3DnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) { + MailServer ms( rr->target, rr->priority ); + result.append( ms ); + } + cached->next(); + } + delete cached; + return result; +} + + +/*! + \class Q3Dns::Server + \brief The Q3Dns::Server class is described in Q3Dns::servers(). + + \internal +*/ + +/*! + Returns a list of servers if the record type is \c Srv. The class + Q3Dns::Server contains the following public variables: + \list + \i QString Q3Dns::Server::name + \i Q_UINT16 Q3Dns::Server::priority + \i Q_UINT16 Q3Dns::Server::weight + \i Q_UINT16 Q3Dns::Server::port + \endlist + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3dns.cpp 3 +*/ +Q3ValueList<Q3Dns::Server> Q3Dns::servers() const +{ +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::servers (%s)", l.ascii() ); +#endif + Q3ValueList<Q3Dns::Server> result; + if ( t != Srv ) + return result; + + Q3PtrList<Q3DnsRR> * cached = Q3DnsDomain::cached( this ); + + Q3DnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) { + Server s( rr->target, rr->priority, rr->weight, rr->port ); + result.append( s ); + } + cached->next(); + } + delete cached; + return result; +} + + +/*! + Returns a list of host names if the record type is \c Ptr. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3dns.cpp 4 + +*/ +QStringList Q3Dns::hostNames() const +{ +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::hostNames (%s)", l.ascii() ); +#endif + QStringList result; + if ( t != Ptr ) + return result; + + Q3PtrList<Q3DnsRR> * cached = Q3DnsDomain::cached( this ); + + Q3DnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) { + QString str( rr->target ); + result.append( str ); + } + cached->next(); + } + delete cached; + return result; +} + + +/*! + Returns a list of texts if the record type is \c Txt. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3dns.cpp 5 +*/ +QStringList Q3Dns::texts() const +{ +#if defined(Q3DNS_DEBUG) + qDebug( "Q3Dns::texts (%s)", l.ascii() ); +#endif + QStringList result; + if ( t != Txt ) + return result; + + Q3PtrList<Q3DnsRR> * cached = Q3DnsDomain::cached( this ); + + Q3DnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) { + QString str( rr->text ); + result.append( str ); + } + cached->next(); + } + delete cached; + return result; +} + + +/*! + Returns the canonical name for this DNS node. (This works + regardless of what recordType() is set to.) + + If the canonical name isn't known, this function returns a null + string. + + The canonical name of a DNS node is its full name, or the full + name of the target of its CNAME. For example, if l.trolltech.com + is a CNAME to lillian.troll.no, and the search path for Q3Dns is + "trolltech.com", then the canonical name for all of "lillian", + "l", "lillian.troll.no." and "l.trolltech.com" is + "lillian.troll.no.". +*/ + +QString Q3Dns::canonicalName() const +{ + // the cname should work regardless of the recordType(), so set the record + // type temporarily to cname when you look at the cache + Q3Dns *that = (Q3Dns*) this; // mutable function + RecordType oldType = t; + that->t = Cname; + Q3PtrList<Q3DnsRR> * cached = Q3DnsDomain::cached( that ); + that->t = oldType; + + Q3DnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain && rr->domain ) { + delete cached; + return rr->target; + } + cached->next(); + } + delete cached; + return QString(); +} + +#if defined(Q_DNS_SYNCHRONOUS) +/*! \reimp +*/ +void Q3Dns::connectNotify( const char *signal ) +{ + if ( d->noEventLoop && qstrcmp(signal,SIGNAL(resultsReady()) )==0 ) { + doSynchronousLookup(); + } +} +#endif + +#if defined(Q_OS_WIN32) || defined(Q_OS_CYGWIN) + +#if defined(Q_DNS_SYNCHRONOUS) +void Q3Dns::doSynchronousLookup() +{ + // ### not implemented yet +} +#endif + +// the following typedefs are needed for GetNetworkParams() API call +#ifndef IP_TYPES_INCLUDED +#define MAX_HOSTNAME_LEN 128 +#define MAX_DOMAIN_NAME_LEN 128 +#define MAX_SCOPE_ID_LEN 256 +typedef struct { + char String[4 * 4]; +} IP_ADDRESS_STRING, *PIP_ADDRESS_STRING, IP_MASK_STRING, *PIP_MASK_STRING; +typedef struct _IP_ADDR_STRING { + struct _IP_ADDR_STRING* Next; + IP_ADDRESS_STRING IpAddress; + IP_MASK_STRING IpMask; + DWORD Context; +} IP_ADDR_STRING, *PIP_ADDR_STRING; +typedef struct { + char HostName[MAX_HOSTNAME_LEN + 4] ; + char DomainName[MAX_DOMAIN_NAME_LEN + 4]; + PIP_ADDR_STRING CurrentDnsServer; + IP_ADDR_STRING DnsServerList; + UINT NodeType; + char ScopeId[MAX_SCOPE_ID_LEN + 4]; + UINT EnableRouting; + UINT EnableProxy; + UINT EnableDns; +} FIXED_INFO, *PFIXED_INFO; +#endif +typedef DWORD (WINAPI *GNP)( PFIXED_INFO, PULONG ); + +// ### FIXME: this code is duplicated in qfiledialog.cpp +static QString getWindowsRegString( HKEY key, const QString &subKey ) +{ + QString s; + QT_WA( { + char buf[1024]; + DWORD bsz = sizeof(buf); + int r = RegQueryValueEx( key, (TCHAR*)subKey.ucs2(), 0, 0, (LPBYTE)buf, &bsz ); + if ( r == ERROR_SUCCESS ) { + s = QString::fromUcs2( (unsigned short *)buf ); + } else if ( r == ERROR_MORE_DATA ) { + char *ptr = new char[bsz+1]; + r = RegQueryValueEx( key, (TCHAR*)subKey.ucs2(), 0, 0, (LPBYTE)ptr, &bsz ); + if ( r == ERROR_SUCCESS ) + s = QLatin1String(ptr); + delete [] ptr; + } + } , { + char buf[512]; + DWORD bsz = sizeof(buf); + int r = RegQueryValueExA( key, subKey.local8Bit(), 0, 0, (LPBYTE)buf, &bsz ); + if ( r == ERROR_SUCCESS ) { + s = QLatin1String(buf); + } else if ( r == ERROR_MORE_DATA ) { + char *ptr = new char[bsz+1]; + r = RegQueryValueExA( key, subKey.local8Bit(), 0, 0, (LPBYTE)ptr, &bsz ); + if ( r == ERROR_SUCCESS ) + s = QLatin1String(ptr); + delete [] ptr; + } + } ); + return s; +} + +static bool getDnsParamsFromRegistry( const QString &path, + QString *domainName, QString *nameServer, QString *searchList ) +{ + HKEY k; + int r; + QT_WA( { + r = RegOpenKeyEx( HKEY_LOCAL_MACHINE, + (TCHAR*)path.ucs2(), + 0, KEY_READ, &k ); + } , { + r = RegOpenKeyExA( HKEY_LOCAL_MACHINE, + path.latin1(), + 0, KEY_READ, &k ); + } ); + + if ( r == ERROR_SUCCESS ) { + *domainName = getWindowsRegString( k, QLatin1String("DhcpDomain") ); + if ( domainName->isEmpty() ) + *domainName = getWindowsRegString( k, QLatin1String("Domain") ); + + *nameServer = getWindowsRegString( k, QLatin1String("DhcpNameServer") ); + if ( nameServer->isEmpty() ) + *nameServer = getWindowsRegString( k, QLatin1String("NameServer") ); + + *searchList = getWindowsRegString( k, QLatin1String("SearchList") ); + } + RegCloseKey( k ); + return r == ERROR_SUCCESS; +} + +void Q3Dns::doResInit() +{ + char separator = 0; + + if ( theNs ) + delete theNs; + theNs = new Q3PtrList<QHostAddress>; + theNs->setAutoDelete( true ); + theDomains = new Q3StrList( true ); + theDomains->setAutoDelete( true ); + + QString domainName, nameServer, searchList; + + bool gotNetworkParams = false; + // try the API call GetNetworkParams() first and use registry lookup only + // as a fallback +#ifdef Q_OS_WINCE + HINSTANCE hinstLib = LoadLibraryW( L"iphlpapi" ); +#else + HINSTANCE hinstLib = LoadLibraryA( "iphlpapi" ); +#endif + if ( hinstLib != 0 ) { +#ifdef Q_OS_WINCE + GNP getNetworkParams = (GNP) GetProcAddressW( hinstLib, L"GetNetworkParams" ); +#else + GNP getNetworkParams = (GNP) GetProcAddress( hinstLib, "GetNetworkParams" ); +#endif + if ( getNetworkParams != 0 ) { + ULONG l = 0; + DWORD res; + res = getNetworkParams( 0, &l ); + if ( res == ERROR_BUFFER_OVERFLOW ) { + FIXED_INFO *finfo = (FIXED_INFO*)new char[l]; + res = getNetworkParams( finfo, &l ); + if ( res == ERROR_SUCCESS ) { + domainName = QLatin1String(finfo->DomainName); + nameServer = QLatin1String(""); + IP_ADDR_STRING *dnsServer = &finfo->DnsServerList; + while ( dnsServer != 0 ) { + nameServer += QLatin1String(dnsServer->IpAddress.String); + dnsServer = dnsServer->Next; + if ( dnsServer != 0 ) + nameServer += QLatin1String(" "); + } + searchList = QLatin1String(""); + separator = ' '; + gotNetworkParams = true; + } + delete[] finfo; + } + } + FreeLibrary( hinstLib ); + } + if ( !gotNetworkParams ) { + if ( getDnsParamsFromRegistry( + QString( QLatin1String("System\\CurrentControlSet\\Services\\Tcpip\\Parameters") ), + &domainName, &nameServer, &searchList )) { + // for NT + separator = ' '; + } else if ( getDnsParamsFromRegistry( + QString( QLatin1String("System\\CurrentControlSet\\Services\\VxD\\MSTCP") ), + &domainName, &nameServer, &searchList )) { + // for Windows 98 + separator = ','; + } else { + // Could not access the TCP/IP parameters + domainName = QLatin1String(""); + nameServer = QLatin1String("127.0.0.1"); + searchList = QLatin1String(""); + separator = ' '; + } + } + + nameServer = nameServer.simplifyWhiteSpace(); + int first, last; + if ( !nameServer.isEmpty() ) { + first = 0; + do { + last = nameServer.find( QLatin1Char(separator), first ); + if ( last < 0 ) + last = nameServer.length(); + Q3Dns tmp( nameServer.mid( first, last-first ), Q3Dns::A ); + Q3ValueList<QHostAddress> address = tmp.addresses(); + Q_LONG i = address.count(); + while( i ) + theNs->append( new QHostAddress(address[--i]) ); + first = last+1; + } while( first < (int)nameServer.length() ); + } + + searchList = searchList + QLatin1String(" ") + domainName; + searchList = searchList.simplifyWhiteSpace().lower(); + first = 0; + do { + last = searchList.find( QLatin1Char(separator), first ); + if ( last < 0 ) + last = searchList.length(); + theDomains->append( qstrdup( searchList.mid( first, last-first ).latin1() ) ); + first = last+1; + } while( first < (int)searchList.length() ); +} + +#elif defined(Q_OS_UNIX) + +#if defined(Q_DNS_SYNCHRONOUS) +void Q3Dns::doSynchronousLookup() +{ + if ( t!=None && !l.isEmpty() ) { + Q3ValueListIterator<QString> it = n.begin(); + Q3ValueListIterator<QString> end = n.end(); + int type; + switch( t ) { + case Q3Dns::A: + type = 1; + break; + case Q3Dns::Aaaa: + type = 28; + break; + case Q3Dns::Mx: + type = 15; + break; + case Q3Dns::Srv: + type = 33; + break; + case Q3Dns::Cname: + type = 5; + break; + case Q3Dns::Ptr: + type = 12; + break; + case Q3Dns::Txt: + type = 16; + break; + default: + type = (char)255; // any + break; + } + while( it != end ) { + QString s = *it; + it++; + QByteArray ba( 512 ); + int len = res_search( s.latin1(), 1, type, (uchar*)ba.data(), ba.size() ); + if ( len > 0 ) { + ba.resize( len ); + + Q3DnsQuery * query = new Q3DnsQuery; + query->started = now(); + query->id = ++theId; + query->t = t; + query->l = s; + Q3DnsAnswer a( ba, query ); + a.parse(); + } else if ( len == -1 ) { + // res_search error + } + } + emit resultsReady(); + } +} +#endif + +#if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 3))) +#define Q_MODERN_RES_API +#endif + +void Q3Dns::doResInit() +{ + if ( theNs ) + return; + theNs = new Q3PtrList<QHostAddress>; + theNs->setAutoDelete( true ); + theDomains = new Q3StrList( true ); + theDomains->setAutoDelete( true ); + + // read resolv.conf manually. + QFile resolvConf(QLatin1String("/etc/resolv.conf")); + if (resolvConf.open(QIODevice::ReadOnly)) { + QTextStream stream( &resolvConf ); + QString line; + + while ( !stream.atEnd() ) { + line = stream.readLine(); + QStringList list = QStringList::split( QLatin1String(" "), line ); + if( line.startsWith( QLatin1String("#") ) || list.size() < 2 ) + continue; + const QString type = list[0].lower(); + + if ( type == QLatin1String("nameserver") ) { + QHostAddress *address = new QHostAddress(); + if ( address->setAddress( QString(list[1]) ) ) { + // only add ipv6 addresses from resolv.conf if + // this host supports ipv6. + if ( address->isIPv4Address() || ipv6support ) + theNs->append( address ); + else + delete address; + } else { + delete address; + } + } else if ( type == QLatin1String("search") ) { + QStringList srch = QStringList::split( QLatin1String(" "), list[1] ); + for ( QStringList::Iterator i = srch.begin(); i != srch.end(); ++i ) + theDomains->append( (*i).lower().local8Bit() ); + + } else if ( type == QLatin1String("domain") ) { + theDomains->append( list[1].lower().local8Bit() ); + } + } + } + + if (theNs->isEmpty()) { +#if defined(Q_MODERN_RES_API) + struct __res_state res; + res_ninit( &res ); + int i; + // find the name servers to use + for( i=0; i < MAXNS && i < res.nscount; i++ ) + theNs->append( new QHostAddress( ntohl( res.nsaddr_list[i].sin_addr.s_addr ) ) ); +# if defined(MAXDFLSRCH) + for( i=0; i < MAXDFLSRCH; i++ ) { + if ( res.dnsrch[i] && *(res.dnsrch[i]) ) + theDomains->append( QString::fromLatin1( res.dnsrch[i] ).lower().local8Bit() ); + else + break; + } +# endif + if ( *res.defdname ) + theDomains->append( QString::fromLatin1( res.defdname ).lower().local8Bit() ); +#else + res_init(); + int i; + // find the name servers to use + for( i=0; i < MAXNS && i < _res.nscount; i++ ) + theNs->append( new QHostAddress( ntohl( _res.nsaddr_list[i].sin_addr.s_addr ) ) ); +# if defined(MAXDFLSRCH) + for( i=0; i < MAXDFLSRCH; i++ ) { + if ( _res.dnsrch[i] && *(_res.dnsrch[i]) ) + theDomains->append( QString::fromLatin1( _res.dnsrch[i] ).lower().local8Bit() ); + else + break; + } +# endif + if ( *_res.defdname ) + theDomains->append( QString::fromLatin1( _res.defdname ).lower().local8Bit() ); +#endif + + // the code above adds "0.0.0.0" as a name server at the slightest + // hint of trouble. so remove those again. + theNs->first(); + while( theNs->current() ) { + if ( theNs->current()->isNull() ) + delete theNs->take(); + else + theNs->next(); + } + } + + QFile hosts( QString::fromLatin1( "/etc/hosts" ) ); + if ( hosts.open( QIODevice::ReadOnly ) ) { + // read the /etc/hosts file, creating long-life A and PTR RRs + // for the things we find. + QTextStream i( &hosts ); + QString line; + while( !i.atEnd() ) { + line = i.readLine().simplifyWhiteSpace().lower(); + uint n = 0; + while( (int) n < line.length() && line[(int)n] != QLatin1Char('#') ) + n++; + line.truncate( n ); + n = 0; + while( (int) n < line.length() && !line[(int)n].isSpace() ) + n++; + QString ip = line.left( n ); + QHostAddress a; + a.setAddress( ip ); + if ( ( a.isIPv4Address() || a.isIPv6Address() ) && !a.isNull() ) { + bool first = true; + line = line.mid( n+1 ); + n = 0; + while( (int) n < line.length() && !line[(int)n].isSpace() ) + n++; + QString hostname = line.left( n ); + // ### in case of bad syntax, hostname is invalid. do we care? + if ( n ) { + Q3DnsRR * rr = new Q3DnsRR( hostname ); + if ( a.isIPv4Address() ) + rr->t = Q3Dns::A; + else + rr->t = Q3Dns::Aaaa; + rr->address = a; + rr->deleteTime = UINT_MAX; + rr->expireTime = UINT_MAX; + rr->current = true; + if ( first ) { + first = false; + Q3DnsRR * ptr = new Q3DnsRR( Q3Dns::toInAddrArpaDomain( a ) ); + ptr->t = Q3Dns::Ptr; + ptr->target = hostname; + ptr->deleteTime = UINT_MAX; + ptr->expireTime = UINT_MAX; + ptr->current = true; + } + } + } + } + } +} + +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_DNS diff --git a/src/qt3support/network/q3dns.h b/src/qt3support/network/q3dns.h new file mode 100644 index 0000000..bfe3e8b --- /dev/null +++ b/src/qt3support/network/q3dns.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3DNS_H +#define Q3DNS_H + +#include <QtCore/qobject.h> +#include <QtNetwork/qhostaddress.h> +#include <QtCore/qsocketnotifier.h> +#include <QtCore/qstringlist.h> +#include <Qt3Support/q3valuelist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +#ifndef QT_NO_DNS + +//#define Q_DNS_SYNCHRONOUS + +class Q3DnsPrivate; + +class Q_COMPAT_EXPORT Q3Dns: public QObject { + Q_OBJECT +public: + enum RecordType { + None, + A, Aaaa, + Mx, Srv, + Cname, + Ptr, + Txt + }; + + Q3Dns(); + Q3Dns( const QString & label, RecordType rr = A ); + Q3Dns( const QHostAddress & address, RecordType rr = Ptr ); + virtual ~Q3Dns(); + + // to set/change the query + virtual void setLabel( const QString & label ); + virtual void setLabel( const QHostAddress & address ); + QString label() const { return l; } + + virtual void setRecordType( RecordType rr = A ); + RecordType recordType() const { return t; } + + // whether something is happening behind the scenes + bool isWorking() const; + + // to query for replies + Q3ValueList<QHostAddress> addresses() const; + + class Q_COMPAT_EXPORT MailServer { + public: + MailServer( const QString & n=QString(), Q_UINT16 p=0 ) + :name(n), priority(p) {} + QString name; + Q_UINT16 priority; + Q_DUMMY_COMPARISON_OPERATOR(MailServer) + }; + Q3ValueList<MailServer> mailServers() const; + + class Q_COMPAT_EXPORT Server { + public: + Server(const QString & n=QString(), Q_UINT16 p=0, Q_UINT16 w=0, Q_UINT16 po=0 ) + : name(n), priority(p), weight(w), port(po) {} + QString name; + Q_UINT16 priority; + Q_UINT16 weight; + Q_UINT16 port; + Q_DUMMY_COMPARISON_OPERATOR(Server) + }; + Q3ValueList<Server> servers() const; + + QStringList hostNames() const; + + QStringList texts() const; + + QString canonicalName() const; // ### real-world but uncommon: QStringList + + QStringList qualifiedNames() const { return n; } + +#if defined(Q_DNS_SYNCHRONOUS) +protected: + void connectNotify( const char *signal ); +#endif + +Q_SIGNALS: + void resultsReady(); + +private Q_SLOTS: + void startQuery(); + +private: + static void doResInit(); + void setStartQueryTimer(); + static QString toInAddrArpaDomain( const QHostAddress &address ); +#if defined(Q_DNS_SYNCHRONOUS) + void doSynchronousLookup(); +#endif + + QString l; + QStringList n; + RecordType t; + Q3DnsPrivate * d; + + friend class Q3DnsAnswer; + friend class Q3DnsManager; +}; + + +// Q3DnsSocket are sockets that are used for DNS lookup + +class Q3DnsSocket: public QObject { + Q_OBJECT + // note: Private not public. This class contains NO public API. +protected: + Q3DnsSocket( QObject *, const char * ); + virtual ~Q3DnsSocket(); + +private Q_SLOTS: + virtual void cleanCache(); + virtual void retransmit(); + virtual void answer(); +}; + +#endif // QT_NO_DNS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3DNS_H diff --git a/src/qt3support/network/q3ftp.cpp b/src/qt3support/network/q3ftp.cpp new file mode 100644 index 0000000..5ab84cc --- /dev/null +++ b/src/qt3support/network/q3ftp.cpp @@ -0,0 +1,2378 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "q3ftp.h" + +#ifndef QT_NO_NETWORKPROTOCOL_FTP + +#include "q3socket.h" +#include "q3socketdevice.h" +#include "qurlinfo.h" +#include "q3urloperator.h" +#include "qstringlist.h" +#include "qregexp.h" +#include "qtimer.h" +#include "qfileinfo.h" +#include "q3ptrdict.h" +#include "q3cstring.h" +#include "qcoreapplication.h" +#include "qftp.h" + +#ifndef QT_NO_TEXTCODEC +#include "qtextcodec.h" +#endif + +QT_BEGIN_NAMESPACE + +//#define Q3FTPPI_DEBUG +//#define Q3FTPDTP_DEBUG + +class Q3FtpPI; + +class Q3FtpDTP : public QObject +{ + Q_OBJECT + +public: + enum ConnectState { + CsHostFound, + CsConnected, + CsClosed, + CsHostNotFound, + CsConnectionRefused + }; + + Q3FtpDTP( Q3FtpPI *p, QObject *parent=0, const char *name=0 ); + + void setData( QByteArray * ); + void setDevice( QIODevice * ); + void writeData(); + + void setBytesTotal( int bytes ) + { + bytesTotal = bytes; + bytesDone = 0; + emit dataTransferProgress( bytesDone, bytesTotal ); + } + + bool hasError() const; + QString errorMessage() const; + void clearError(); + + void connectToHost( const QString & host, Q_UINT16 port ) + { socket.connectToHost( host, port ); } + + Q3Socket::State socketState() const + { return socket.state(); } + + Q_ULONG bytesAvailable() const + { return socket.bytesAvailable(); } + + Q_LONG readBlock( char *data, Q_ULONG maxlen ) + { + Q_LONG read = socket.readBlock( data, maxlen ); + bytesDone += read; + return read; + } + + QByteArray readAll() + { + QByteArray tmp = socket.readAll(); + bytesDone += tmp.size(); + return tmp; + } + + void abortConnection(); + + static bool parseDir( const QString &buffer, const QString &userName, QUrlInfo *info ); + +signals: + void listInfo( const QUrlInfo& ); + void readyRead(); + void dataTransferProgress( int, int ); + + void connectState( int ); + +private slots: + void socketConnected(); + void socketReadyRead(); + void socketError( int ); + void socketConnectionClosed(); + void socketBytesWritten( int ); + +private: + void clearData() + { + is_ba = false; + data.dev = 0; + } + + Q3Socket socket; + Q3FtpPI *pi; + QString err; + int bytesDone; + int bytesTotal; + bool callWriteData; + + // If is_ba is true, ba is used; ba is never 0. + // Otherwise dev is used; dev can be 0 or not. + union { + QByteArray *ba; + QIODevice *dev; + } data; + bool is_ba; +}; + +class Q3FtpPI : public QObject +{ + Q_OBJECT + +public: + Q3FtpPI( QObject *parent = 0 ); + + void connectToHost( const QString &host, Q_UINT16 port ); + + bool sendCommands( const QStringList &cmds ); + bool sendCommand( const QString &cmd ) + { return sendCommands( QStringList( cmd ) ); } + + void clearPendingCommands(); + void abort(); + + QString currentCommand() const + { return currentCmd; } + + bool rawCommand; + + Q3FtpDTP dtp; // the PI has a DTP which is not the design of RFC 959, but it + // makes the design simpler this way +signals: + void connectState( int ); + void finished( const QString& ); + void error( int, const QString& ); + void rawFtpReply( int, const QString& ); + +private slots: + void hostFound(); + void connected(); + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void error( int ); + + void dtpConnectState( int ); + +private: + // the states are modelled after the generalized state diagram of RFC 959, + // page 58 + enum State { + Begin, + Idle, + Waiting, + Success, + Failure + }; + + enum AbortState { + None, + AbortStarted, + WaitForAbortToFinish + }; + + bool processReply(); + bool startNextCmd(); + + Q3Socket commandSocket; + QString replyText; + signed char replyCode[3]; + State state; + AbortState abortState; + QStringList pendingCommands; + QString currentCmd; + + bool waitForDtpToConnect; + bool waitForDtpToClose; +}; + +/********************************************************************** + * + * Q3FtpCommand implemenatation + * + *********************************************************************/ +class Q3FtpCommand +{ +public: + Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw ); + Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, const QByteArray &ba ); + Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, QIODevice *dev ); + ~Q3FtpCommand(); + + int id; + Q3Ftp::Command command; + QStringList rawCmds; + + // If is_ba is true, ba is used; ba is never 0. + // Otherwise dev is used; dev can be 0 or not. + union { + QByteArray *ba; + QIODevice *dev; + } data; + bool is_ba; + + static int idCounter; +}; + +int Q3FtpCommand::idCounter = 0; + +Q3FtpCommand::Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw ) + : command(cmd), rawCmds(raw), is_ba(false) +{ + id = ++idCounter; + data.dev = 0; +} + +Q3FtpCommand::Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, const QByteArray &ba ) + : command(cmd), rawCmds(raw), is_ba(true) +{ + id = ++idCounter; + data.ba = new QByteArray( ba ); +} + +Q3FtpCommand::Q3FtpCommand( Q3Ftp::Command cmd, QStringList raw, QIODevice *dev ) + : command(cmd), rawCmds(raw), is_ba(false) +{ + id = ++idCounter; + data.dev = dev; +} + +Q3FtpCommand::~Q3FtpCommand() +{ + if ( is_ba ) + delete data.ba; +} + +/********************************************************************** + * + * Q3FtpDTP implemenatation + * + *********************************************************************/ +Q3FtpDTP::Q3FtpDTP( Q3FtpPI *p, QObject *parent, const char *name ) : + QObject( parent, name ), + socket( 0, "Q3FtpDTP_socket" ), + pi( p ), + callWriteData( false ) +{ + clearData(); + + connect( &socket, SIGNAL(connected()), + SLOT(socketConnected()) ); + connect( &socket, SIGNAL(readyRead()), + SLOT(socketReadyRead()) ); + connect( &socket, SIGNAL(error(int)), + SLOT(socketError(int)) ); + connect( &socket, SIGNAL(connectionClosed()), + SLOT(socketConnectionClosed()) ); + connect( &socket, SIGNAL(bytesWritten(int)), + SLOT(socketBytesWritten(int)) ); +} + +void Q3FtpDTP::setData( QByteArray *ba ) +{ + is_ba = true; + data.ba = ba; +} + +void Q3FtpDTP::setDevice( QIODevice *dev ) +{ + is_ba = false; + data.dev = dev; +} + +void Q3FtpDTP::writeData() +{ + if ( is_ba ) { +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::writeData: write %d bytes", data.ba->size() ); +#endif + if ( data.ba->size() == 0 ) + emit dataTransferProgress( 0, bytesTotal ); + else + socket.writeBlock( data.ba->data(), data.ba->size() ); + socket.close(); + clearData(); + } else if ( data.dev ) { + callWriteData = false; + const int blockSize = 16*1024; + char buf[blockSize]; + while ( !data.dev->atEnd() && socket.bytesToWrite()==0 ) { + Q_LONG read = data.dev->readBlock( buf, blockSize ); +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::writeData: writeBlock() of size %d bytes", (int)read ); +#endif + socket.writeBlock( buf, read ); + if ( !data.dev ) + return; // this can happen when a command is aborted + } + if ( data.dev->atEnd() ) { + if ( bytesDone==0 && socket.bytesToWrite()==0 ) + emit dataTransferProgress( 0, bytesTotal ); + socket.close(); + clearData(); + } else { + callWriteData = true; + } + } +} + +inline bool Q3FtpDTP::hasError() const +{ + return !err.isNull(); +} + +inline QString Q3FtpDTP::errorMessage() const +{ + return err; +} + +inline void Q3FtpDTP::clearError() +{ + err.clear(); +} + +void Q3FtpDTP::abortConnection() +{ +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::abortConnection" ); +#endif + callWriteData = false; + clearData(); + + socket.clearPendingData(); + socket.close(); +} + +bool Q3FtpDTP::parseDir( const QString &buffer, const QString &userName, QUrlInfo *info ) +{ + QStringList lst = QStringList::split( QLatin1String(" "), buffer ); + + if ( lst.count() < 9 ) + return false; + + QString tmp; + + // permissions + tmp = lst[ 0 ]; + + if ( tmp[ 0 ] == QChar( QLatin1Char('d') ) ) { + info->setDir( true ); + info->setFile( false ); + info->setSymLink( false ); + } else if ( tmp[ 0 ] == QChar( QLatin1Char('-') ) ) { + info->setDir( false ); + info->setFile( true ); + info->setSymLink( false ); + } else if ( tmp[ 0 ] == QChar( QLatin1Char('l') ) ) { + info->setDir( true ); // #### todo + info->setFile( false ); + info->setSymLink( true ); + } else { + return false; + } + + static int user = 0; + static int group = 1; + static int other = 2; + static int readable = 0; + static int writable = 1; + static int executable = 2; + + bool perms[ 3 ][ 3 ]; + perms[0][0] = (tmp[ 1 ] == QLatin1Char('r')); + perms[0][1] = (tmp[ 2 ] == QLatin1Char('w')); + perms[0][2] = (tmp[ 3 ] == QLatin1Char('x')); + perms[1][0] = (tmp[ 4 ] == QLatin1Char('r')); + perms[1][1] = (tmp[ 5 ] == QLatin1Char('w')); + perms[1][2] = (tmp[ 6 ] == QLatin1Char('x')); + perms[2][0] = (tmp[ 7 ] == QLatin1Char('r')); + perms[2][1] = (tmp[ 8 ] == QLatin1Char('w')); + perms[2][2] = (tmp[ 9 ] == QLatin1Char('x')); + + // owner + tmp = lst[ 2 ]; + info->setOwner( tmp ); + + // group + tmp = lst[ 3 ]; + info->setGroup( tmp ); + + // ### not correct + info->setWritable( ( userName == info->owner() && perms[ user ][ writable ] ) || + perms[ other ][ writable ] ); + info->setReadable( ( userName == info->owner() && perms[ user ][ readable ] ) || + perms[ other ][ readable ] ); + + int p = 0; + if ( perms[ user ][ readable ] ) + p |= QUrlInfo::ReadOwner; + if ( perms[ user ][ writable ] ) + p |= QUrlInfo::WriteOwner; + if ( perms[ user ][ executable ] ) + p |= QUrlInfo::ExeOwner; + if ( perms[ group ][ readable ] ) + p |= QUrlInfo::ReadGroup; + if ( perms[ group ][ writable ] ) + p |= QUrlInfo::WriteGroup; + if ( perms[ group ][ executable ] ) + p |= QUrlInfo::ExeGroup; + if ( perms[ other ][ readable ] ) + p |= QUrlInfo::ReadOther; + if ( perms[ other ][ writable ] ) + p |= QUrlInfo::WriteOther; + if ( perms[ other ][ executable ] ) + p |= QUrlInfo::ExeOther; + info->setPermissions( p ); + + // size + tmp = lst[ 4 ]; + info->setSize( tmp.toInt() ); + + // date and time + QTime time; + QString dateStr; + dateStr += QLatin1String("Sun "); + lst[ 5 ][ 0 ] = lst[ 5 ][ 0 ].upper(); + dateStr += lst[ 5 ]; + dateStr += QLatin1Char(' '); + dateStr += lst[ 6 ]; + dateStr += QLatin1Char(' '); + + if ( lst[ 7 ].contains( QLatin1String(":") ) ) { + time = QTime( lst[ 7 ].left( 2 ).toInt(), lst[ 7 ].right( 2 ).toInt() ); + dateStr += QString::number( QDate::currentDate().year() ); + } else { + dateStr += lst[ 7 ]; + } + + QDate date = QDate::fromString( dateStr ); + info->setLastModified( QDateTime( date, time ) ); + + if ( lst[ 7 ].contains( QLatin1String(":") ) ) { + const int futureTolerance = 600; + if( info->lastModified().secsTo( QDateTime::currentDateTime() ) < -futureTolerance ) { + QDateTime dt = info->lastModified(); + QDate d = dt.date(); + d.setYMD(d.year()-1, d.month(), d.day()); + dt.setDate(d); + info->setLastModified(dt); + } + } + + // name + if ( info->isSymLink() ) + info->setName( lst[ 8 ].stripWhiteSpace() ); + else { + QString n; + for ( uint i = 8; i < (uint) lst.count(); ++i ) + n += lst[ i ] + QLatin1String(" "); + n = n.stripWhiteSpace(); + info->setName( n ); + } + return true; +} + +void Q3FtpDTP::socketConnected() +{ +#if !defined (Q_WS_QWS) + // Use a large send buffer to reduce the number + // of writeBlocks when download and uploading files. + // The actual size used here (128k) is default on most + // Unixes. + socket.socketDevice()->setSendBufferSize(128 * 1024); + socket.socketDevice()->setReceiveBufferSize(128 * 1024); +#endif + + bytesDone = 0; +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::connectState( CsConnected )" ); +#endif + emit connectState( Q3FtpDTP::CsConnected ); +} + +void Q3FtpDTP::socketReadyRead() +{ + if ( pi->currentCommand().isEmpty() ) { + socket.close(); +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::connectState( CsClosed )" ); +#endif + emit connectState( Q3FtpDTP::CsClosed ); + return; + } + + if ( pi->currentCommand().startsWith(QLatin1String("LIST")) ) { + while ( socket.canReadLine() ) { + QUrlInfo i; + QString line = QLatin1String(socket.readLine()); +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP read (list): '%s'", line.latin1() ); +#endif + if ( parseDir( line, QLatin1String(""), &i ) ) { + emit listInfo( i ); + } else { + // some FTP servers don't return a 550 if the file or directory + // does not exist, but rather write a text to the data socket + // -- try to catch these cases + if ( line.endsWith( QLatin1String("No such file or directory\r\n") ) ) + err = line; + } + } + } else { + if ( !is_ba && data.dev ) { + QByteArray ba( socket.bytesAvailable() ); + Q_LONG bytesRead = socket.readBlock( ba.data(), ba.size() ); + if ( bytesRead < 0 ) { + // ### error handling + return; + } + ba.resize( bytesRead ); + bytesDone += bytesRead; +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP read: %d bytes (total %d bytes)", (int)bytesRead, bytesDone ); +#endif + emit dataTransferProgress( bytesDone, bytesTotal ); + if (data.dev) // make sure it wasn't deleted in the slot + data.dev->writeBlock( ba ); + } else { +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP readyRead: %d bytes available (total %d bytes read)", (int)bytesAvailable(), bytesDone ); +#endif + emit dataTransferProgress( bytesDone+socket.bytesAvailable(), bytesTotal ); + emit readyRead(); + } + } +} + +void Q3FtpDTP::socketError( int e ) +{ + if ( e == Q3Socket::ErrHostNotFound ) { +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::connectState( CsHostNotFound )" ); +#endif + emit connectState( Q3FtpDTP::CsHostNotFound ); + } else if ( e == Q3Socket::ErrConnectionRefused ) { +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::connectState( CsConnectionRefused )" ); +#endif + emit connectState( Q3FtpDTP::CsConnectionRefused ); + } +} + +void Q3FtpDTP::socketConnectionClosed() +{ + if ( !is_ba && data.dev ) { + clearData(); + } +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::connectState( CsClosed )" ); +#endif + emit connectState( Q3FtpDTP::CsClosed ); +} + +void Q3FtpDTP::socketBytesWritten( int bytes ) +{ + bytesDone += bytes; +#if defined(Q3FTPDTP_DEBUG) + qDebug( "Q3FtpDTP::bytesWritten( %d )", bytesDone ); +#endif + emit dataTransferProgress( bytesDone, bytesTotal ); + if ( callWriteData ) + writeData(); +} + +/********************************************************************** + * + * Q3FtpPI implemenatation + * + *********************************************************************/ +Q3FtpPI::Q3FtpPI( QObject *parent ) : + QObject( parent ), + rawCommand(false), + dtp( this ), + commandSocket( 0, "Q3FtpPI_socket" ), + state( Begin ), abortState( None ), + currentCmd( QString() ), + waitForDtpToConnect( false ), + waitForDtpToClose( false ) +{ + connect( &commandSocket, SIGNAL(hostFound()), + SLOT(hostFound()) ); + connect( &commandSocket, SIGNAL(connected()), + SLOT(connected()) ); + connect( &commandSocket, SIGNAL(connectionClosed()), + SLOT(connectionClosed()) ); + connect( &commandSocket, SIGNAL(delayedCloseFinished()), + SLOT(delayedCloseFinished()) ); + connect( &commandSocket, SIGNAL(readyRead()), + SLOT(readyRead()) ); + connect( &commandSocket, SIGNAL(error(int)), + SLOT(error(int)) ); + + connect( &dtp, SIGNAL(connectState(int)), + SLOT(dtpConnectState(int)) ); +} + +void Q3FtpPI::connectToHost( const QString &host, Q_UINT16 port ) +{ + emit connectState( Q3Ftp::HostLookup ); + commandSocket.connectToHost( host, port ); +} + +/* + Sends the sequence of commands \a cmds to the FTP server. When the commands + are all done the finished() signal is emitted. When an error occurs, the + error() signal is emitted. + + If there are pending commands in the queue this functions returns false and + the \a cmds are not added to the queue; otherwise it returns true. +*/ +bool Q3FtpPI::sendCommands( const QStringList &cmds ) +{ + if ( !pendingCommands.isEmpty() ) + return false; + + if ( commandSocket.state()!=Q3Socket::Connected || state!=Idle ) { + emit error( Q3Ftp::NotConnected, QFtp::tr( "Not connected" ) ); + return true; // there are no pending commands + } + + pendingCommands = cmds; + startNextCmd(); + return true; +} + +void Q3FtpPI::clearPendingCommands() +{ + pendingCommands.clear(); + dtp.abortConnection(); + currentCmd.clear(); + state = Idle; +} + +void Q3FtpPI::abort() +{ + pendingCommands.clear(); + + if ( abortState != None ) + // ABOR already sent + return; + + abortState = AbortStarted; +#if defined(Q3FTPPI_DEBUG) + qDebug( "Q3FtpPI send: ABOR" ); +#endif + commandSocket.writeBlock( "ABOR\r\n", 6 ); + + if ( currentCmd.startsWith(QLatin1String("STOR ")) ) + dtp.abortConnection(); +} + +void Q3FtpPI::hostFound() +{ + emit connectState( Q3Ftp::Connecting ); +} + +void Q3FtpPI::connected() +{ + state = Begin; +#if defined(Q3FTPPI_DEBUG) +// qDebug( "Q3FtpPI state: %d [connected()]", state ); +#endif + emit connectState( Q3Ftp::Connected ); +} + +void Q3FtpPI::connectionClosed() +{ + commandSocket.close(); + emit connectState( Q3Ftp::Unconnected ); +} + +void Q3FtpPI::delayedCloseFinished() +{ + emit connectState( Q3Ftp::Unconnected ); +} + +void Q3FtpPI::error( int e ) +{ + if ( e == Q3Socket::ErrHostNotFound ) { + emit connectState( Q3Ftp::Unconnected ); + emit error( Q3Ftp::HostNotFound, + QFtp::tr( "Host %1 not found" ).arg( commandSocket.peerName() ) ); + } else if ( e == Q3Socket::ErrConnectionRefused ) { + emit connectState( Q3Ftp::Unconnected ); + emit error( Q3Ftp::ConnectionRefused, + QFtp::tr( "Connection refused to host %1" ).arg( commandSocket.peerName() ) ); + } +} + +void Q3FtpPI::readyRead() +{ + if ( waitForDtpToClose ) + return; + + while ( commandSocket.canReadLine() ) { + // read line with respect to line continuation + QString line = QLatin1String(commandSocket.readLine()); + if ( replyText.isEmpty() ) { + if ( line.length() < 3 ) { + // ### protocol error + return; + } + const int lowerLimit[3] = {1,0,0}; + const int upperLimit[3] = {5,5,9}; + for ( int i=0; i<3; i++ ) { + replyCode[i] = line[i].digitValue(); + if ( replyCode[i]<lowerLimit[i] || replyCode[i]>upperLimit[i] ) { + // ### protocol error + return; + } + } + } + QString endOfMultiLine; + endOfMultiLine[0] = '0' + replyCode[0]; + endOfMultiLine[1] = '0' + replyCode[1]; + endOfMultiLine[2] = '0' + replyCode[2]; + endOfMultiLine[3] = ' '; + QString lineCont( endOfMultiLine ); + lineCont[3] = '-'; + QString lineLeft4 = line.left(4); + + while ( lineLeft4 != endOfMultiLine ) { + if ( lineLeft4 == lineCont ) + replyText += line.mid( 4 ); // strip 'xyz-' + else + replyText += line; + if ( !commandSocket.canReadLine() ) + return; + line = QLatin1String(commandSocket.readLine()); + lineLeft4 = line.left(4); + } + replyText += line.mid( 4 ); // strip reply code 'xyz ' + if ( replyText.endsWith(QLatin1String("\r\n")) ) + replyText.truncate( replyText.length()-2 ); + + if ( processReply() ) + replyText = QLatin1String(""); + } +} + +/* + Process a reply from the FTP server. + + Returns true if the reply was processed or false if the reply has to be + processed at a later point. +*/ +bool Q3FtpPI::processReply() +{ +#if defined(Q3FTPPI_DEBUG) +// qDebug( "Q3FtpPI state: %d [processReply() begin]", state ); + if ( replyText.length() < 400 ) + qDebug( "Q3FtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.latin1() ); + else + qDebug( "Q3FtpPI recv: %d (text skipped)", 100*replyCode[0]+10*replyCode[1]+replyCode[2] ); +#endif + + // process 226 replies ("Closing Data Connection") only when the data + // connection is really closed to avoid short reads of the DTP + if ( 100*replyCode[0]+10*replyCode[1]+replyCode[2] == 226 ) { + if ( dtp.socketState() != Q3Socket::Idle ) { + waitForDtpToClose = true; + return false; + } + } + + switch ( abortState ) { + case AbortStarted: + abortState = WaitForAbortToFinish; + break; + case WaitForAbortToFinish: + abortState = None; + return true; + default: + break; + } + + // get new state + static const State table[5] = { + /* 1yz 2yz 3yz 4yz 5yz */ + Waiting, Success, Idle, Failure, Failure + }; + switch ( state ) { + case Begin: + if ( replyCode[0] == 1 ) { + return true; + } else if ( replyCode[0] == 2 ) { + state = Idle; + emit finished( QFtp::tr( "Connected to host %1" ).arg( commandSocket.peerName() ) ); + break; + } + // ### error handling + return true; + case Waiting: + if ( replyCode[0]<0 || replyCode[0]>5 ) + state = Failure; + else +#if defined(Q_OS_IRIX) && defined(Q_CC_GNU) + { + // work around a crash on 64 bit gcc IRIX + State *t = (State *) table; + state = t[replyCode[0] - 1]; + } +#else + state = table[replyCode[0] - 1]; +#endif + break; + default: + // ### spontaneous message + return true; + } +#if defined(Q3FTPPI_DEBUG) +// qDebug( "Q3FtpPI state: %d [processReply() intermediate]", state ); +#endif + + // special actions on certain replies + int replyCodeInt = 100*replyCode[0] + 10*replyCode[1] + replyCode[2]; + emit rawFtpReply( replyCodeInt, replyText ); + if ( rawCommand ) { + rawCommand = false; + } else if ( replyCodeInt == 227 ) { + // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) + // rfc959 does not define this response precisely, and gives + // both examples where the parenthesis are used, and where + // they are missing. We need to scan for the address and host + // info. + QRegExp addrPortPattern(QLatin1String("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)")); + if (addrPortPattern.search(replyText) == -1) { +#if defined(Q3FTPPI_DEBUG) + qDebug( "Q3Ftp: bad 227 response -- address and port information missing" ); +#endif + // ### error handling + } else { + QStringList lst = addrPortPattern.capturedTexts(); + QString host = lst[1] + QLatin1String(".") + lst[2] + QLatin1String(".") + lst[3] + QLatin1String(".") + lst[4]; + Q_UINT16 port = ( lst[5].toUInt() << 8 ) + lst[6].toUInt(); + waitForDtpToConnect = true; + dtp.connectToHost( host, port ); + } + } else if ( replyCodeInt == 230 ) { + if ( currentCmd.startsWith(QLatin1String("USER ")) && pendingCommands.count()>0 && + pendingCommands.first().startsWith(QLatin1String("PASS ")) ) { + // no need to send the PASS -- we are already logged in + pendingCommands.pop_front(); + } + // 230 User logged in, proceed. + emit connectState( Q3Ftp::LoggedIn ); + } else if ( replyCodeInt == 213 ) { + // 213 File status. + if ( currentCmd.startsWith(QLatin1String("SIZE ")) ) + dtp.setBytesTotal( replyText.simplifyWhiteSpace().toInt() ); + } else if ( replyCode[0]==1 && currentCmd.startsWith(QLatin1String("STOR ")) ) { + dtp.writeData(); + } + + // react on new state + switch ( state ) { + case Begin: + // ### should never happen + break; + case Success: + // ### success handling + state = Idle; + // no break! + case Idle: + if ( dtp.hasError() ) { + emit error( Q3Ftp::UnknownError, dtp.errorMessage() ); + dtp.clearError(); + } + startNextCmd(); + break; + case Waiting: + // ### do nothing + break; + case Failure: + emit error( Q3Ftp::UnknownError, replyText ); + state = Idle; + startNextCmd(); + break; + } +#if defined(Q3FTPPI_DEBUG) +// qDebug( "Q3FtpPI state: %d [processReply() end]", state ); +#endif + return true; +} + +#ifndef QT_NO_TEXTCODEC +Q_COMPAT_EXPORT QTextCodec *qt_ftp_filename_codec = 0; +#endif + +/* + Starts next pending command. Returns false if there are no pending commands, + otherwise it returns true. +*/ +bool Q3FtpPI::startNextCmd() +{ + if ( waitForDtpToConnect ) + // don't process any new commands until we are connected + return true; + +#if defined(Q3FTPPI_DEBUG) + if ( state != Idle ) + qDebug( "Q3FtpPI startNextCmd: Internal error! Q3FtpPI called in non-Idle state %d", state ); +#endif + if ( pendingCommands.isEmpty() ) { + currentCmd.clear(); + emit finished( replyText ); + return false; + } + currentCmd = pendingCommands.first(); + pendingCommands.pop_front(); +#if defined(Q3FTPPI_DEBUG) + qDebug( "Q3FtpPI send: %s", currentCmd.left( currentCmd.length()-2 ).latin1() ); +#endif + state = Waiting; +#ifndef QT_NO_TEXTCODEC + if ( qt_ftp_filename_codec ) { + int len; + Q3CString enc = qt_ftp_filename_codec->fromUnicode(currentCmd,len); + commandSocket.writeBlock( enc.data(), len ); + } else +#endif + { + commandSocket.writeBlock( currentCmd.latin1(), currentCmd.length() ); + } + return true; +} + +void Q3FtpPI::dtpConnectState( int s ) +{ + switch ( s ) { + case Q3FtpDTP::CsClosed: + if ( waitForDtpToClose ) { + // there is an unprocessed reply + if ( processReply() ) + replyText = QLatin1String(""); + else + return; + } + waitForDtpToClose = false; + readyRead(); + return; + case Q3FtpDTP::CsConnected: + waitForDtpToConnect = false; + startNextCmd(); + return; + case Q3FtpDTP::CsHostNotFound: + case Q3FtpDTP::CsConnectionRefused: + emit error( Q3Ftp::ConnectionRefused, + QFtp::tr( "Connection refused for data connection" ) ); + startNextCmd(); + return; + default: + return; + } +} + +/********************************************************************** + * + * Q3FtpPrivate + * + *********************************************************************/ +class Q3FtpPrivate +{ +public: + Q3FtpPrivate() : + close_waitForStateChange(false), + state( Q3Ftp::Unconnected ), + error( Q3Ftp::NoError ), + npWaitForLoginDone( false ) + { pending.setAutoDelete( true ); } + + Q3FtpPI pi; + Q3PtrList<Q3FtpCommand> pending; + bool close_waitForStateChange; + Q3Ftp::State state; + Q3Ftp::Error error; + QString errorString; + + bool npWaitForLoginDone; +}; + +static Q3PtrDict<Q3FtpPrivate> *d_ptr = 0; +static void cleanup_d_ptr() +{ + delete d_ptr; + d_ptr = 0; +} +static Q3FtpPrivate* dHelper( const Q3Ftp* foo ) +{ + if ( !d_ptr ) { + d_ptr = new Q3PtrDict<Q3FtpPrivate>; + d_ptr->setAutoDelete( true ); + qAddPostRoutine( cleanup_d_ptr ); + } + Q3FtpPrivate* ret = d_ptr->find( (void*)foo ); + if ( ! ret ) { + ret = new Q3FtpPrivate; + d_ptr->replace( (void*) foo, ret ); + } + return ret; +} + +static void delete_d( const Q3Ftp* foo ) +{ + if ( d_ptr ) + d_ptr->remove( (void*) foo ); +} + +/********************************************************************** + * + * Q3Ftp implementation + * + *********************************************************************/ +/*! + \class Q3Ftp + \brief The Q3Ftp class provides an implementation of the FTP protocol. + + \compat + + This class provides two different interfaces: one is the + QNetworkProtocol interface that allows you to use FTP through the + QUrlOperator abstraction. The other is a direct interface to FTP + that gives you lower-level access to the FTP protocol for finer + control. Using the direct interface you can also execute arbitrary + FTP commands. + + Don't mix the two interfaces, since the behavior is not + well-defined. + + If you want to use Q3Ftp with the QNetworkProtocol interface, you + do not use it directly, but rather through a QUrlOperator, for + example: + + \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 0 + + This code will only work if the Q3Ftp class is registered; to + register the class, you must call q3InitNetworkProtocols() before + using a QUrlOperator with Q3Ftp. + + The rest of this descrption describes the direct interface to FTP. + + The class works asynchronously, so there are no blocking + functions. If an operation cannot be executed immediately, the + function will still return straight away and the operation will be + scheduled for later execution. The results of scheduled operations + are reported via signals. This approach depends on the event loop + being in operation. + + The operations that can be scheduled (they are called "commands" + in the rest of the documentation) are the following: + connectToHost(), login(), close(), list(), cd(), get(), put(), + remove(), mkdir(), rmdir(), rename() and rawCommand(). + + All of these commands return a unique identifier that allows you + to keep track of the command that is currently being executed. + When the execution of a command starts, the commandStarted() + signal with the command's identifier is emitted. When the command + is finished, the commandFinished() signal is emitted with the + command's identifier and a bool that indicates whether the command + finished with an error. + + In some cases, you might want to execute a sequence of commands, + e.g. if you want to connect and login to a FTP server. This is + simply achieved: + + \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 1 + + In this case two FTP commands have been scheduled. When the last + scheduled command has finished, a done() signal is emitted with + a bool argument that tells you whether the sequence finished with + an error. + + If an error occurs during the execution of one of the commands in + a sequence of commands, all the pending commands (i.e. scheduled, + but not yet executed commands) are cleared and no signals are + emitted for them. + + Some commands, e.g. list(), emit additional signals to report + their results. + + Example: If you want to download the INSTALL file from Trolltech's + FTP server, you would write this: + + \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 2 + + For this example the following sequence of signals is emitted + (with small variations, depending on network traffic, etc.): + + \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 3 + + The dataTransferProgress() signal in the above example is useful + if you want to show a \link QProgressBar progress bar \endlink to + inform the user about the progress of the download. The + readyRead() signal tells you that there is data ready to be read. + The amount of data can be queried then with the bytesAvailable() + function and it can be read with the readBlock() or readAll() + function. + + If the login fails for the above example, the signals would look + like this: + + \snippet doc/src/snippets/code/src_qt3support_network_q3ftp.cpp 4 + + You can then get details about the error with the error() and + errorString() functions. + + The functions currentId() and currentCommand() provide more + information about the currently executing command. + + The functions hasPendingCommands() and clearPendingCommands() + allow you to query and clear the list of pending commands. + + The safest and easiest way to use the FTP protocol is to use + QUrlOperator() or the FTP commands described above. If you are an + experienced network programmer and want to have complete control + you can use rawCommand() to execute arbitrary FTP commands. + + \sa Q3NetworkProtocol, Q3UrlOperator Q3Http +*/ + +/*! + Constructs a Q3Ftp object. +*/ +Q3Ftp::Q3Ftp() : Q3NetworkProtocol() +{ + init(); +} + +/*! + Constructs a Q3Ftp object. The \a parent and \a name parameters + are passed to the QObject constructor. +*/ +Q3Ftp::Q3Ftp( QObject *parent, const char *name ) : Q3NetworkProtocol() +{ + if ( parent ) + parent->insertChild( this ); + setName( name ); + init(); +} + +void Q3Ftp::init() +{ + Q3FtpPrivate *d = dHelper( this ); + d->errorString = QFtp::tr( "Unknown error" ); + + connect( &d->pi, SIGNAL(connectState(int)), + SLOT(piConnectState(int)) ); + connect( &d->pi, SIGNAL(finished(QString)), + SLOT(piFinished(QString)) ); + connect( &d->pi, SIGNAL(error(int,QString)), + SLOT(piError(int,QString)) ); + connect( &d->pi, SIGNAL(rawFtpReply(int,QString)), + SLOT(piFtpReply(int,QString)) ); + + connect( &d->pi.dtp, SIGNAL(readyRead()), + SIGNAL(readyRead()) ); + connect( &d->pi.dtp, SIGNAL(dataTransferProgress(int,int)), + SIGNAL(dataTransferProgress(int,int)) ); + connect( &d->pi.dtp, SIGNAL(listInfo(QUrlInfo)), + SIGNAL(listInfo(QUrlInfo)) ); +} + +/*! + \enum Q3Ftp::State + + This enum defines the connection state: + + \value Unconnected There is no connection to the host. + \value HostLookup A host name lookup is in progress. + \value Connecting An attempt to connect to the host is in progress. + \value Connected Connection to the host has been achieved. + \value LoggedIn Connection and user login have been achieved. + \value Closing The connection is closing down, but it is not yet + closed. (The state will be \c Unconnected when the connection is + closed.) + + \sa stateChanged() state() +*/ +/*! + \enum Q3Ftp::Error + + This enum identifies the error that occurred. + + \value NoError No error occurred. + \value HostNotFound The host name lookup failed. + \value ConnectionRefused The server refused the connection. + \value NotConnected Tried to send a command, but there is no connection to + a server. + \value UnknownError An error other than those specified above + occurred. + + \sa error() +*/ + +/*! + \enum Q3Ftp::Command + + This enum is used as the return value for the currentCommand() function. + This allows you to perform specific actions for particular + commands, e.g. in a FTP client, you might want to clear the + directory view when a list() command is started; in this case you + can simply check in the slot connected to the start() signal if + the currentCommand() is \c List. + + \value None No command is being executed. + \value ConnectToHost connectToHost() is being executed. + \value Login login() is being executed. + \value Close close() is being executed. + \value List list() is being executed. + \value Cd cd() is being executed. + \value Get get() is being executed. + \value Put put() is being executed. + \value Remove remove() is being executed. + \value Mkdir mkdir() is being executed. + \value Rmdir rmdir() is being executed. + \value Rename rename() is being executed. + \value RawCommand rawCommand() is being executed. + + \sa currentCommand() +*/ + +/*! + \fn void Q3Ftp::stateChanged( int state ) + + This signal is emitted when the state of the connection changes. + The argument \a state is the new state of the connection; it is + one of the \l State values. + + It is usually emitted in response to a connectToHost() or close() + command, but it can also be emitted "spontaneously", e.g. when the + server closes the connection unexpectedly. + + \sa connectToHost() close() state() State +*/ + +/*! + \fn void Q3Ftp::listInfo( const QUrlInfo &i ); + + This signal is emitted for each directory entry the list() command + finds. The details of the entry are stored in \a i. + + \sa list() +*/ + +/*! + \fn void Q3Ftp::commandStarted( int id ) + + This signal is emitted when processing the command identified by + \a id starts. + + \sa commandFinished() done() +*/ + +/*! + \fn void Q3Ftp::commandFinished( int id, bool error ) + + This signal is emitted when processing the command identified by + \a id has finished. \a error is true if an error occurred during + the processing; otherwise \a error is false. + + \sa commandStarted() done() error() errorString() +*/ + +/*! + \fn void Q3Ftp::done( bool error ) + + This signal is emitted when the last pending command has finished; + (it is emitted after the last command's commandFinished() signal). + \a error is true if an error occurred during the processing; + otherwise \a error is false. + + \sa commandFinished() error() errorString() +*/ + +/*! + \fn void Q3Ftp::readyRead() + + This signal is emitted in response to a get() command when there + is new data to read. + + If you specify a device as the second argument in the get() + command, this signal is \e not emitted; instead the data is + written directly to the device. + + You can read the data with the readAll() or readBlock() functions. + + This signal is useful if you want to process the data in chunks as + soon as it becomes available. If you are only interested in the + complete data, just connect to the commandFinished() signal and + read the data then instead. + + \sa get() readBlock() readAll() bytesAvailable() +*/ + +/*! + \fn void Q3Ftp::dataTransferProgress( int done, int total ) + + This signal is emitted in response to a get() or put() request to + indicate the current progress of the download or upload. + + \a done is the amount of data that has already been transferred + and \a total is the total amount of data to be read or written. It + is possible that the Q3Ftp class is not able to determine the total + amount of data that should be transferred, in which case \a total + is 0. (If you connect this signal to a QProgressBar, the progress + bar shows a busy indicator if the total is 0). + + \warning \a done and \a total are not necessarily the size in + bytes, since for large files these values might need to be + "scaled" to avoid overflow. + + \sa get() put() +*/ + +/*! + \fn void Q3Ftp::rawCommandReply( int replyCode, const QString &detail ); + + This signal is emitted in response to the rawCommand() function. + \a replyCode is the 3 digit reply code and \a detail is the text + that follows the reply code. + + \sa rawCommand() +*/ + +/*! + Connects to the FTP server \a host using port \a port. + + The stateChanged() signal is emitted when the state of the + connecting process changes, e.g. to \c HostLookup, then \c + Connecting, then \c Connected. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa stateChanged() commandStarted() commandFinished() +*/ +int Q3Ftp::connectToHost( const QString &host, Q_UINT16 port ) +{ + QStringList cmds; + cmds << host; + cmds << QString::number( (uint)port ); + return addCommand( new Q3FtpCommand( ConnectToHost, cmds ) ); +} + +/*! + Logs in to the FTP server with the username \a user and the + password \a password. + + The stateChanged() signal is emitted when the state of the + connecting process changes, e.g. to \c LoggedIn. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int Q3Ftp::login( const QString &user, const QString &password ) +{ + QStringList cmds; + cmds << ( QString(QLatin1String("USER ")) + ( user.isNull() ? QString(QLatin1String("anonymous")) : user ) + QLatin1String("\r\n") ); + cmds << ( QString(QLatin1String("PASS ")) + ( password.isNull() ? QString(QLatin1String("anonymous@")) : password ) + QLatin1String("\r\n") ); + return addCommand( new Q3FtpCommand( Login, cmds ) ); +} + +/*! + Closes the connection to the FTP server. + + The stateChanged() signal is emitted when the state of the + connecting process changes, e.g. to \c Closing, then \c + Unconnected. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa stateChanged() commandStarted() commandFinished() +*/ +int Q3Ftp::close() +{ + return addCommand( new Q3FtpCommand( Close, QStringList(QLatin1String("QUIT\r\n")) ) ); +} + +/*! + Lists the contents of directory \a dir on the FTP server. If \a + dir is empty, it lists the contents of the current directory. + + The listInfo() signal is emitted for each directory entry found. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa listInfo() commandStarted() commandFinished() +*/ +int Q3Ftp::list( const QString &dir ) +{ + QStringList cmds; + cmds << QLatin1String("TYPE A\r\n"); + cmds << QLatin1String("PASV\r\n"); + if ( dir.isEmpty() ) + cmds << QLatin1String("LIST\r\n"); + else + cmds << ( QLatin1String("LIST ") + dir + QLatin1String("\r\n") ); + return addCommand( new Q3FtpCommand( List, cmds ) ); +} + +/*! + Changes the working directory of the server to \a dir. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int Q3Ftp::cd( const QString &dir ) +{ + return addCommand( new Q3FtpCommand( Cd, QStringList(QLatin1String("CWD ")+dir+QLatin1String("\r\n")) ) ); +} + +/*! + Downloads the file \a file from the server. + + If \a dev is 0, then the readyRead() signal is emitted when there + is data available to read. You can then read the data with the + readBlock() or readAll() functions. + + If \a dev is not 0, the data is written directly to the device \a + dev. Make sure that the \a dev pointer is valid for the duration + of the operation (it is safe to delete it when the + commandFinished() signal is emitted). In this case the readyRead() + signal is \e not emitted and you cannot read data with the + readBlock() or readAll() functions. + + If you don't read the data immediately it becomes available, i.e. + when the readyRead() signal is emitted, it is still available + until the next command is started. + + For example, if you want to present the data to the user as soon + as there is something available, connect to the readyRead() signal + and read the data immediately. On the other hand, if you only want + to work with the complete data, you can connect to the + commandFinished() signal and read the data when the get() command + is finished. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa readyRead() dataTransferProgress() commandStarted() + commandFinished() +*/ +int Q3Ftp::get( const QString &file, QIODevice *dev ) +{ + QStringList cmds; + cmds << ( QLatin1String("SIZE ") + file + QLatin1String("\r\n") ); + cmds << QLatin1String("TYPE I\r\n"); + cmds << QLatin1String("PASV\r\n"); + cmds << ( QLatin1String("RETR ") + file + QLatin1String("\r\n") ); + if ( dev ) + return addCommand( new Q3FtpCommand( Get, cmds, dev ) ); + return addCommand( new Q3FtpCommand( Get, cmds ) ); +} + +/*! + \overload + + Writes the data \a data to the file called \a file on the server. + The progress of the upload is reported by the + dataTransferProgress() signal. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa dataTransferProgress() commandStarted() commandFinished() +*/ +int Q3Ftp::put( const QByteArray &data, const QString &file ) +{ + QStringList cmds; + cmds << QLatin1String("TYPE I\r\n"); + cmds << QLatin1String("PASV\r\n"); + cmds << ( QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n") ); + cmds << ( QLatin1String("STOR ") + file + QLatin1String("\r\n") ); + return addCommand( new Q3FtpCommand( Put, cmds, data ) ); +} + +/*! + Reads the data from the IO device \a dev, and writes it to the + file called \a file on the server. The data is read in chunks from + the IO device, so this overload allows you to transmit large + amounts of data without the need to read all the data into memory + at once. + + Make sure that the \a dev pointer is valid for the duration of the + operation (it is safe to delete it when the commandFinished() is + emitted). +*/ +int Q3Ftp::put( QIODevice *dev, const QString &file ) +{ + QStringList cmds; + cmds << QLatin1String("TYPE I\r\n"); + cmds << QLatin1String("PASV\r\n"); + if ( !dev->isSequentialAccess() ) + cmds << ( QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n") ); + cmds << ( QLatin1String("STOR ") + file + QLatin1String("\r\n") ); + return addCommand( new Q3FtpCommand( Put, cmds, dev ) ); +} + +/*! + Deletes the file called \a file from the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int Q3Ftp::remove( const QString &file ) +{ + return addCommand( new Q3FtpCommand( Remove, QStringList(QLatin1String("DELE ")+file+QLatin1String("\r\n")) ) ); +} + +/*! + Creates a directory called \a dir on the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int Q3Ftp::mkdir( const QString &dir ) +{ + return addCommand( new Q3FtpCommand( Mkdir, QStringList(QLatin1String("MKD ")+dir+QLatin1String("\r\n")) ) ); +} + +/*! + Removes the directory called \a dir from the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int Q3Ftp::rmdir( const QString &dir ) +{ + return addCommand( new Q3FtpCommand( Rmdir, QStringList(QLatin1String("RMD ")+dir+QLatin1String("\r\n")) ) ); +} + +/*! + Renames the file called \a oldname to \a newname on the server. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa commandStarted() commandFinished() +*/ +int Q3Ftp::rename( const QString &oldname, const QString &newname ) +{ + QStringList cmds; + cmds << ( QLatin1String("RNFR ") + oldname + QLatin1String("\r\n") ); + cmds << ( QLatin1String("RNTO ") + newname + QLatin1String("\r\n") ); + return addCommand( new Q3FtpCommand( Rename, cmds ) ); +} + +/*! + Sends the raw FTP command \a command to the FTP server. This is + useful for low-level FTP access. If the operation you wish to + perform has an equivalent Q3Ftp function, we recommend using the + function instead of raw FTP commands since the functions are + easier and safer. + + The function does not block and returns immediately. The command + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + commandStarted() and commandFinished(). + + When the command is started the commandStarted() signal is + emitted. When it is finished the commandFinished() signal is + emitted. + + \sa rawCommandReply() commandStarted() commandFinished() +*/ +int Q3Ftp::rawCommand( const QString &command ) +{ + QString cmd = command.stripWhiteSpace() + QLatin1String("\r\n"); + return addCommand( new Q3FtpCommand( RawCommand, QStringList(cmd) ) ); +} + +/*! + Returns the number of bytes that can be read from the data socket + at the moment. + + \sa get() readyRead() readBlock() readAll() +*/ +Q_ULONG Q3Ftp::bytesAvailable() const +{ + Q3FtpPrivate *d = dHelper( this ); + return d->pi.dtp.bytesAvailable(); +} + +/*! + Reads \a maxlen bytes from the data socket into \a data and + returns the number of bytes read. Returns -1 if an error occurred. + + \sa get() readyRead() bytesAvailable() readAll() +*/ +Q_LONG Q3Ftp::readBlock( char *data, Q_ULONG maxlen ) +{ + Q3FtpPrivate *d = dHelper( this ); + return d->pi.dtp.readBlock( data, maxlen ); +} + +/*! + Reads all the bytes available from the data socket and returns + them. + + \sa get() readyRead() bytesAvailable() readBlock() +*/ +QByteArray Q3Ftp::readAll() +{ + Q3FtpPrivate *d = dHelper( this ); + return d->pi.dtp.readAll(); +} + +/*! + Aborts the current command and deletes all scheduled commands. + + If there is an unfinished command (i.e. a command for which the + commandStarted() signal has been emitted, but for which the + commandFinished() signal has not been emitted), this function + sends an \c ABORT command to the server. When the server replies + that the command is aborted, the commandFinished() signal with the + \c error argument set to \c true is emitted for the command. Due + to timing issues, it is possible that the command had already + finished before the abort request reached the server, in which + case, the commandFinished() signal is emitted with the \c error + argument set to \c false. + + For all other commands that are affected by the abort(), no + signals are emitted. + + If you don't start further FTP commands directly after the + abort(), there won't be any scheduled commands and the done() + signal is emitted. + + \warning Some FTP servers, for example the BSD FTP daemon (version + 0.3), wrongly return a positive reply even when an abort has + occurred. For these servers the commandFinished() signal has its + error flag set to \c false, even though the command did not + complete successfully. + + \sa clearPendingCommands() +*/ +void Q3Ftp::abort() +{ + Q3FtpPrivate *d = dHelper( this ); + if ( d->pending.isEmpty() ) + return; + + clearPendingCommands(); + d->pi.abort(); +} + +/*! + Returns the identifier of the FTP command that is being executed + or 0 if there is no command being executed. + + \sa currentCommand() +*/ +int Q3Ftp::currentId() const +{ + Q3FtpPrivate *d = dHelper( this ); + Q3FtpCommand *c = d->pending.getFirst(); + if ( c == 0 ) + return 0; + return c->id; +} + +/*! + Returns the command type of the FTP command being executed or \c + None if there is no command being executed. + + \sa currentId() +*/ +Q3Ftp::Command Q3Ftp::currentCommand() const +{ + Q3FtpPrivate *d = dHelper( this ); + Q3FtpCommand *c = d->pending.getFirst(); + if ( c == 0 ) + return None; + return c->command; +} + +/*! + Returns the QIODevice pointer that is used by the FTP command to read data + from or store data to. If there is no current FTP command being executed or + if the command does not use an IO device, this function returns 0. + + This function can be used to delete the QIODevice in the slot connected to + the commandFinished() signal. + + \sa get() put() +*/ +QIODevice* Q3Ftp::currentDevice() const +{ + Q3FtpPrivate *d = dHelper( this ); + Q3FtpCommand *c = d->pending.getFirst(); + if ( !c ) + return 0; + if ( c->is_ba ) + return 0; + return c->data.dev; +} + +/*! + Returns true if there are any commands scheduled that have not yet + been executed; otherwise returns false. + + The command that is being executed is \e not considered as a + scheduled command. + + \sa clearPendingCommands() currentId() currentCommand() +*/ +bool Q3Ftp::hasPendingCommands() const +{ + Q3FtpPrivate *d = dHelper( this ); + return d->pending.count() > 1; +} + +/*! + Deletes all pending commands from the list of scheduled commands. + This does not affect the command that is being executed. If you + want to stop this as well, use abort(). + + \sa hasPendingCommands() abort() +*/ +void Q3Ftp::clearPendingCommands() +{ + Q3FtpPrivate *d = dHelper( this ); + Q3FtpCommand *c = 0; + if ( d->pending.count() > 0 ) + c = d->pending.take( 0 ); + d->pending.clear(); + if ( c ) + d->pending.append( c ); +} + +/*! + Returns the current state of the object. When the state changes, + the stateChanged() signal is emitted. + + \sa State stateChanged() +*/ +Q3Ftp::State Q3Ftp::state() const +{ + Q3FtpPrivate *d = dHelper( this ); + return d->state; +} + +/*! + Returns the last error that occurred. This is useful to find out + what when wrong when receiving a commandFinished() or a done() + signal with the \c error argument set to \c true. + + If you start a new command, the error status is reset to \c NoError. +*/ +Q3Ftp::Error Q3Ftp::error() const +{ + Q3FtpPrivate *d = dHelper( this ); + return d->error; +} + +/*! + Returns a human-readable description of the last error that + occurred. This is useful for presenting a error message to the + user when receiving a commandFinished() or a done() signal with + the \c error argument set to \c true. + + The error string is often (but not always) the reply from the + server, so it is not always possible to translate the string. If + the message comes from Qt, the string has already passed through + tr(). +*/ +QString Q3Ftp::errorString() const +{ + Q3FtpPrivate *d = dHelper( this ); + return d->errorString; +} + +int Q3Ftp::addCommand( Q3FtpCommand *cmd ) +{ + Q3FtpPrivate *d = dHelper( this ); + d->pending.append( cmd ); + + if ( d->pending.count() == 1 ) + // don't emit the commandStarted() signal before the id is returned + QTimer::singleShot( 0, this, SLOT(startNextCommand()) ); + + return cmd->id; +} + +void Q3Ftp::startNextCommand() +{ + Q3FtpPrivate *d = dHelper( this ); + + Q3FtpCommand *c = d->pending.getFirst(); + if ( c == 0 ) + return; + + d->error = NoError; + d->errorString = QFtp::tr( "Unknown error" ); + + if ( bytesAvailable() ) + readAll(); // clear the data + emit commandStarted( c->id ); + + if ( c->command == ConnectToHost ) { + d->pi.connectToHost( c->rawCmds[0], c->rawCmds[1].toUInt() ); + } else { + if ( c->command == Put ) { + if ( c->is_ba ) { + d->pi.dtp.setData( c->data.ba ); + d->pi.dtp.setBytesTotal( c->data.ba->size() ); + } else if ( c->data.dev && (c->data.dev->isOpen() || c->data.dev->open(QIODevice::ReadOnly)) ) { + d->pi.dtp.setDevice( c->data.dev ); + if ( c->data.dev->isSequentialAccess() ) + d->pi.dtp.setBytesTotal( 0 ); + else + d->pi.dtp.setBytesTotal( c->data.dev->size() ); + } + } else if ( c->command == Get ) { + if ( !c->is_ba && c->data.dev ) { + d->pi.dtp.setDevice( c->data.dev ); + } + } else if ( c->command == Close ) { + d->state = Q3Ftp::Closing; + emit stateChanged( d->state ); + } + if ( !d->pi.sendCommands( c->rawCmds ) ) { + // ### error handling (this case should not happen) + } + } +} + +void Q3Ftp::piFinished( const QString& ) +{ + Q3FtpPrivate *d = dHelper( this ); + Q3FtpCommand *c = d->pending.getFirst(); + if ( c == 0 ) + return; + + if ( c->command == Close ) { + // The order of in which the slots are called is arbitrary, so + // disconnect the SIGNAL-SIGNAL temporary to make sure that we + // don't get the commandFinished() signal before the stateChanged() + // signal. + if ( d->state != Q3Ftp::Unconnected ) { + d->close_waitForStateChange = true; + return; + } + } + emit commandFinished( c->id, false ); + + d->pending.removeFirst(); + if ( d->pending.isEmpty() ) { + emit done( false ); + } else { + startNextCommand(); + } +} + +void Q3Ftp::piError( int errorCode, const QString &text ) +{ + Q3FtpPrivate *d = dHelper( this ); + Q3FtpCommand *c = d->pending.getFirst(); + + // non-fatal errors + if ( c->command==Get && d->pi.currentCommand().startsWith(QLatin1String("SIZE ")) ) { + d->pi.dtp.setBytesTotal( -1 ); + return; + } else if ( c->command==Put && d->pi.currentCommand().startsWith(QLatin1String("ALLO ")) ) { + return; + } + + d->error = (Error)errorCode; + switch ( currentCommand() ) { + case ConnectToHost: + d->errorString = QFtp::tr( "Connecting to host failed:\n%1" ).arg( text ); + break; + case Login: + d->errorString = QFtp::tr( "Login failed:\n%1" ).arg( text ); + break; + case List: + d->errorString = QFtp::tr( "Listing directory failed:\n%1" ).arg( text ); + break; + case Cd: + d->errorString = QFtp::tr( "Changing directory failed:\n%1" ).arg( text ); + break; + case Get: + d->errorString = QFtp::tr( "Downloading file failed:\n%1" ).arg( text ); + break; + case Put: + d->errorString = QFtp::tr( "Uploading file failed:\n%1" ).arg( text ); + break; + case Remove: + d->errorString = QFtp::tr( "Removing file failed:\n%1" ).arg( text ); + break; + case Mkdir: + d->errorString = QFtp::tr( "Creating directory failed:\n%1" ).arg( text ); + break; + case Rmdir: + d->errorString = QFtp::tr( "Removing directory failed:\n%1" ).arg( text ); + break; + default: + d->errorString = text; + break; + } + + d->pi.clearPendingCommands(); + clearPendingCommands(); + emit commandFinished( c->id, true ); + + d->pending.removeFirst(); + if ( d->pending.isEmpty() ) + emit done( true ); + else + startNextCommand(); +} + +void Q3Ftp::piConnectState( int state ) +{ + Q3FtpPrivate *d = dHelper( this ); + d->state = (State)state; + emit stateChanged( d->state ); + if ( d->close_waitForStateChange ) { + d->close_waitForStateChange = false; + piFinished( QFtp::tr( "Connection closed" ) ); + } +} + +void Q3Ftp::piFtpReply( int code, const QString &text ) +{ + if ( currentCommand() == RawCommand ) { + Q3FtpPrivate *d = dHelper( this ); + d->pi.rawCommand = true; + emit rawCommandReply( code, text ); + } +} + +/*! + Destructor. +*/ +Q3Ftp::~Q3Ftp() +{ + abort(); + close(); + delete_d( this ); +} + +/********************************************************************** + * + * Q3Ftp implementation of the Q3NetworkProtocol interface + * + *********************************************************************/ +/*! \reimp +*/ +void Q3Ftp::operationListChildren( Q3NetworkOperation *op ) +{ + op->setState( StInProgress ); + + cd( ( url()->path().isEmpty() ? QString( QLatin1String("/") ) : url()->path() ) ); + list(); + emit start( op ); +} + +/*! \reimp +*/ +void Q3Ftp::operationMkDir( Q3NetworkOperation *op ) +{ + op->setState( StInProgress ); + + mkdir( op->arg( 0 ) ); +} + +/*! \reimp +*/ +void Q3Ftp::operationRemove( Q3NetworkOperation *op ) +{ + op->setState( StInProgress ); + + cd( ( url()->path().isEmpty() ? QString( QLatin1String("/") ) : url()->path() ) ); + remove( Q3Url( op->arg( 0 ) ).path() ); +} + +/*! \reimp +*/ +void Q3Ftp::operationRename( Q3NetworkOperation *op ) +{ + op->setState( StInProgress ); + + cd( ( url()->path().isEmpty() ? QString( QLatin1String("/") ) : url()->path() ) ); + rename( op->arg( 0 ), op->arg( 1 )); +} + +/*! \reimp +*/ +void Q3Ftp::operationGet( Q3NetworkOperation *op ) +{ + op->setState( StInProgress ); + + Q3Url u( op->arg( 0 ) ); + get( u.path() ); +} + +/*! \reimp +*/ +void Q3Ftp::operationPut( Q3NetworkOperation *op ) +{ + op->setState( StInProgress ); + + Q3Url u( op->arg( 0 ) ); + put( op->rawArg(1), u.path() ); +} + +/*! \reimp +*/ +bool Q3Ftp::checkConnection( Q3NetworkOperation *op ) +{ + Q3FtpPrivate *d = dHelper( this ); + if ( state() == Unconnected && !d->npWaitForLoginDone ) { + connect( this, SIGNAL(listInfo(QUrlInfo)), + this, SLOT(npListInfo(QUrlInfo)) ); + connect( this, SIGNAL(done(bool)), + this, SLOT(npDone(bool)) ); + connect( this, SIGNAL(stateChanged(int)), + this, SLOT(npStateChanged(int)) ); + connect( this, SIGNAL(dataTransferProgress(int,int)), + this, SLOT(npDataTransferProgress(int,int)) ); + connect( this, SIGNAL(readyRead()), + this, SLOT(npReadyRead()) ); + + d->npWaitForLoginDone = true; + switch ( op->operation() ) { + case OpGet: + case OpPut: + { + Q3Url u( op->arg( 0 ) ); + connectToHost( u.host(), u.port() != -1 ? u.port() : 21 ); + } + break; + default: + connectToHost( url()->host(), url()->port() != -1 ? url()->port() : 21 ); + break; + } + QString user = url()->user().isEmpty() ? QString( QLatin1String("anonymous") ) : url()->user(); + QString pass = url()->password().isEmpty() ? QString( QLatin1String("anonymous@") ) : url()->password(); + login( user, pass ); + } + + if ( state() == LoggedIn ) + return true; + return false; +} + +/*! \reimp +*/ +int Q3Ftp::supportedOperations() const +{ + return OpListChildren | OpMkDir | OpRemove | OpRename | OpGet | OpPut; +} + +/*! \internal + Parses the string, \a buffer, which is one line of a directory + listing which came from the FTP server, and sets the values which + have been parsed to the url info object, \a info. +*/ +void Q3Ftp::parseDir( const QString &buffer, QUrlInfo &info ) +{ + Q3FtpDTP::parseDir( buffer, url()->user(), &info ); +} + +void Q3Ftp::npListInfo( const QUrlInfo & i ) +{ + if ( url() ) { + QRegExp filt( url()->nameFilter(), false, true ); + if ( i.isDir() || filt.search( i.name() ) != -1 ) { + emit newChild( i, operationInProgress() ); + } + } else { + emit newChild( i, operationInProgress() ); + } +} + +void Q3Ftp::npDone( bool err ) +{ + Q3FtpPrivate *d = dHelper( this ); + + bool emitFinishedSignal = false; + Q3NetworkOperation *op = operationInProgress(); + if ( op ) { + if ( err ) { + op->setProtocolDetail( errorString() ); + op->setState( StFailed ); + if ( error() == HostNotFound ) { + op->setErrorCode( (int)ErrHostNotFound ); + } else { + switch ( op->operation() ) { + case OpListChildren: + op->setErrorCode( (int)ErrListChildren ); + break; + case OpMkDir: + op->setErrorCode( (int)ErrMkDir ); + break; + case OpRemove: + op->setErrorCode( (int)ErrRemove ); + break; + case OpRename: + op->setErrorCode( (int)ErrRename ); + break; + case OpGet: + op->setErrorCode( (int)ErrGet ); + break; + case OpPut: + op->setErrorCode( (int)ErrPut ); + break; + } + } + emitFinishedSignal = true; + } else if ( !d->npWaitForLoginDone ) { + switch ( op->operation() ) { + case OpRemove: + emit removed( op ); + break; + case OpMkDir: + { + QUrlInfo inf( op->arg( 0 ), 0, QLatin1String(""), QLatin1String(""), 0, QDateTime(), + QDateTime(), true, false, false, true, true, true ); + emit newChild( inf, op ); + emit createdDirectory( inf, op ); + } + break; + case OpRename: + emit itemChanged( operationInProgress() ); + break; + default: + break; + } + op->setState( StDone ); + emitFinishedSignal = true; + } + } + d->npWaitForLoginDone = false; + + if ( state() == Unconnected ) { + disconnect( this, SIGNAL(listInfo(QUrlInfo)), + this, SLOT(npListInfo(QUrlInfo)) ); + disconnect( this, SIGNAL(done(bool)), + this, SLOT(npDone(bool)) ); + disconnect( this, SIGNAL(stateChanged(int)), + this, SLOT(npStateChanged(int)) ); + disconnect( this, SIGNAL(dataTransferProgress(int,int)), + this, SLOT(npDataTransferProgress(int,int)) ); + disconnect( this, SIGNAL(readyRead()), + this, SLOT(npReadyRead()) ); + } + + // emit the finished() signal at the very end to avoid reentrance problems + if ( emitFinishedSignal ) + emit finished( op ); +} + +void Q3Ftp::npStateChanged( int state ) +{ + if ( url() ) { + if ( state == Connecting ) + emit connectionStateChanged( ConHostFound, QFtp::tr( "Host %1 found" ).arg( url()->host() ) ); + else if ( state == Connected ) + emit connectionStateChanged( ConConnected, QFtp::tr( "Connected to host %1" ).arg( url()->host() ) ); + else if ( state == Unconnected ) + emit connectionStateChanged( ConClosed, QFtp::tr( "Connection to %1 closed" ).arg( url()->host() ) ); + } else { + if ( state == Connecting ) + emit connectionStateChanged( ConHostFound, QFtp::tr( "Host found" ) ); + else if ( state == Connected ) + emit connectionStateChanged( ConConnected, QFtp::tr( "Connected to host" ) ); + else if ( state == Unconnected ) + emit connectionStateChanged( ConClosed, QFtp::tr( "Connection closed" ) ); + } +} + +void Q3Ftp::npDataTransferProgress( int bDone, int bTotal ) +{ + emit Q3NetworkProtocol::dataTransferProgress( bDone, bTotal, operationInProgress() ); +} + +void Q3Ftp::npReadyRead() +{ + emit data( readAll(), operationInProgress() ); +} + +/*! \internal +*/ +void Q3Ftp::hostFound() +{ +} +/*! \internal +*/ +void Q3Ftp::connected() +{ +} +/*! \internal +*/ +void Q3Ftp::closed() +{ +} +/*! \internal +*/ +void Q3Ftp::dataHostFound() +{ +} +/*! \internal +*/ +void Q3Ftp::dataConnected() +{ +} +/*! \internal +*/ +void Q3Ftp::dataClosed() +{ +} +/*! \internal +*/ +void Q3Ftp::dataReadyRead() +{ +} +/*! \internal +*/ +void Q3Ftp::dataBytesWritten( int ) +{ +} +/*! \internal +*/ +void Q3Ftp::error( int ) +{ +} + +QT_END_NAMESPACE + +#include "q3ftp.moc" + +#endif // QT_NO_NETWORKPROTOCOL_FTP diff --git a/src/qt3support/network/q3ftp.h b/src/qt3support/network/q3ftp.h new file mode 100644 index 0000000..685a6af --- /dev/null +++ b/src/qt3support/network/q3ftp.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3FTP_H +#define Q3FTP_H + +#include <QtCore/qstring.h> // char*->QString conversion +#include <QtNetwork/qurlinfo.h> +#include <Qt3Support/q3networkprotocol.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +#ifndef QT_NO_NETWORKPROTOCOL_FTP + +class Q3Socket; +class Q3FtpCommand; + +class Q_COMPAT_EXPORT Q3Ftp : public Q3NetworkProtocol +{ + Q_OBJECT + +public: + Q3Ftp(); // ### Qt 4.0: get rid of this overload + Q3Ftp( QObject *parent, const char *name=0 ); + virtual ~Q3Ftp(); + + int supportedOperations() const; + + // non-Q3NetworkProtocol functions: + enum State { + Unconnected, + HostLookup, + Connecting, + Connected, + LoggedIn, + Closing + }; + enum Error { + NoError, + UnknownError, + HostNotFound, + ConnectionRefused, + NotConnected + }; + enum Command { + None, + ConnectToHost, + Login, + Close, + List, + Cd, + Get, + Put, + Remove, + Mkdir, + Rmdir, + Rename, + RawCommand + }; + + int connectToHost( const QString &host, Q_UINT16 port=21 ); + int login( const QString &user=QString(), const QString &password=QString() ); + int close(); + int list( const QString &dir=QString() ); + int cd( const QString &dir ); + int get( const QString &file, QIODevice *dev=0 ); + int put( const QByteArray &data, const QString &file ); + int put( QIODevice *dev, const QString &file ); + int remove( const QString &file ); + int mkdir( const QString &dir ); + int rmdir( const QString &dir ); + int rename( const QString &oldname, const QString &newname ); + + int rawCommand( const QString &command ); + + Q_ULONG bytesAvailable() const; + Q_LONG readBlock( char *data, Q_ULONG maxlen ); + QByteArray readAll(); + + int currentId() const; + QIODevice* currentDevice() const; + Command currentCommand() const; + bool hasPendingCommands() const; + void clearPendingCommands(); + + State state() const; + + Error error() const; + QString errorString() const; + +public Q_SLOTS: + void abort(); + +Q_SIGNALS: + void stateChanged( int ); + void listInfo( const QUrlInfo& ); + void readyRead(); + void dataTransferProgress( int, int ); + void rawCommandReply( int, const QString& ); + + void commandStarted( int ); + void commandFinished( int, bool ); + void done( bool ); + +protected: + void parseDir( const QString &buffer, QUrlInfo &info ); // ### Qt 4.0: delete this? (not public API) + void operationListChildren( Q3NetworkOperation *op ); + void operationMkDir( Q3NetworkOperation *op ); + void operationRemove( Q3NetworkOperation *op ); + void operationRename( Q3NetworkOperation *op ); + void operationGet( Q3NetworkOperation *op ); + void operationPut( Q3NetworkOperation *op ); + + // ### Qt 4.0: delete these + // unused variables: + Q3Socket *commandSocket, *dataSocket; + bool connectionReady, passiveMode; + int getTotalSize, getDoneSize; + bool startGetOnFail; + int putToWrite, putWritten; + bool errorInListChildren; + +private: + void init(); + int addCommand( Q3FtpCommand * ); + + bool checkConnection( Q3NetworkOperation *op ); + +private Q_SLOTS: + void startNextCommand(); + void piFinished( const QString& ); + void piError( int, const QString& ); + void piConnectState( int ); + void piFtpReply( int, const QString& ); + +private Q_SLOTS: + void npListInfo( const QUrlInfo & ); + void npDone( bool ); + void npStateChanged( int ); + void npDataTransferProgress( int, int ); + void npReadyRead(); + +protected Q_SLOTS: + // ### Qt 4.0: delete these + void hostFound(); + void connected(); + void closed(); + void dataHostFound(); + void dataConnected(); + void dataClosed(); + void dataReadyRead(); + void dataBytesWritten( int nbytes ); + void error( int ); +}; + +#endif // QT_NO_NETWORKPROTOCOL_FTP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3FTP_H diff --git a/src/qt3support/network/q3http.cpp b/src/qt3support/network/q3http.cpp new file mode 100644 index 0000000..f1590a6 --- /dev/null +++ b/src/qt3support/network/q3http.cpp @@ -0,0 +1,2322 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> +#include "q3http.h" + +#ifndef QT_NO_NETWORKPROTOCOL_HTTP + +#include "q3socket.h" +#include "qtextstream.h" +#include "qmap.h" +#include "qstring.h" +#include "qstringlist.h" +#include "q3cstring.h" +#include "qbuffer.h" +#include "q3urloperator.h" +#include "qtimer.h" +#include "private/q3membuf_p.h" +#include "qevent.h" +#include "q3url.h" +#include "qhttp.h" + +QT_BEGIN_NAMESPACE + +//#define Q3HTTP_DEBUG + +class Q3HttpPrivate +{ +public: + Q3HttpPrivate() : + state( Q3Http::Unconnected ), + error( Q3Http::NoError ), + hostname( QString() ), + port( 0 ), + toDevice( 0 ), + postDevice( 0 ), + bytesDone( 0 ), + chunkedSize( -1 ), + idleTimer( 0 ) + { + pending.setAutoDelete( true ); + } + + Q3Socket socket; + Q3PtrList<Q3HttpRequest> pending; + + Q3Http::State state; + Q3Http::Error error; + QString errorString; + + QString hostname; + Q_UINT16 port; + + QByteArray buffer; + QIODevice* toDevice; + QIODevice* postDevice; + + uint bytesDone; + uint bytesTotal; + Q_LONG chunkedSize; + + Q3HttpRequestHeader header; + + bool readHeader; + QString headerStr; + Q3HttpResponseHeader response; + + int idleTimer; + + Q3Membuf rba; +}; + +class Q3HttpRequest +{ +public: + Q3HttpRequest() + { + id = ++idCounter; + } + virtual ~Q3HttpRequest() + { } + + virtual void start( Q3Http * ) = 0; + virtual bool hasRequestHeader(); + virtual Q3HttpRequestHeader requestHeader(); + + virtual QIODevice* sourceDevice() = 0; + virtual QIODevice* destinationDevice() = 0; + + int id; + +private: + static int idCounter; +}; + +int Q3HttpRequest::idCounter = 0; + +bool Q3HttpRequest::hasRequestHeader() +{ + return false; +} + +Q3HttpRequestHeader Q3HttpRequest::requestHeader() +{ + return Q3HttpRequestHeader(); +} + +/**************************************************** + * + * Q3HttpNormalRequest + * + ****************************************************/ + +class Q3HttpNormalRequest : public Q3HttpRequest +{ +public: + Q3HttpNormalRequest( const Q3HttpRequestHeader &h, QIODevice *d, QIODevice *t ) : + header(h), to(t) + { + is_ba = false; + data.dev = d; + } + + Q3HttpNormalRequest( const Q3HttpRequestHeader &h, QByteArray *d, QIODevice *t ) : + header(h), to(t) + { + is_ba = true; + data.ba = d; + } + + ~Q3HttpNormalRequest() + { + if ( is_ba ) + delete data.ba; + } + + void start( Q3Http * ); + bool hasRequestHeader(); + Q3HttpRequestHeader requestHeader(); + + QIODevice* sourceDevice(); + QIODevice* destinationDevice(); + +protected: + Q3HttpRequestHeader header; + +private: + union { + QByteArray *ba; + QIODevice *dev; + } data; + bool is_ba; + QIODevice *to; +}; + +void Q3HttpNormalRequest::start( Q3Http *http ) +{ + http->d->header = header; + + if ( is_ba ) { + http->d->buffer = *data.ba; + if ( http->d->buffer.size() > 0 ) + http->d->header.setContentLength( http->d->buffer.size() ); + + http->d->postDevice = 0; + } else { + http->d->buffer = QByteArray(); + + if ( data.dev && ( data.dev->isOpen() || data.dev->open(IO_ReadOnly) ) ) { + http->d->postDevice = data.dev; + if ( http->d->postDevice->size() > 0 ) + http->d->header.setContentLength( http->d->postDevice->size() ); + } else { + http->d->postDevice = 0; + } + } + + if ( to && ( to->isOpen() || to->open(IO_WriteOnly) ) ) + http->d->toDevice = to; + else + http->d->toDevice = 0; + + http->sendRequest(); +} + +bool Q3HttpNormalRequest::hasRequestHeader() +{ + return true; +} + +Q3HttpRequestHeader Q3HttpNormalRequest::requestHeader() +{ + return header; +} + +QIODevice* Q3HttpNormalRequest::sourceDevice() +{ + if ( is_ba ) + return 0; + return data.dev; +} + +QIODevice* Q3HttpNormalRequest::destinationDevice() +{ + return to; +} + +/**************************************************** + * + * Q3HttpPGHRequest + * (like a Q3HttpNormalRequest, but for the convenience + * functions put(), get() and head() -- i.e. set the + * host header field correctly before sending the + * request) + * + ****************************************************/ + +class Q3HttpPGHRequest : public Q3HttpNormalRequest +{ +public: + Q3HttpPGHRequest( const Q3HttpRequestHeader &h, QIODevice *d, QIODevice *t ) : + Q3HttpNormalRequest( h, d, t ) + { } + + Q3HttpPGHRequest( const Q3HttpRequestHeader &h, QByteArray *d, QIODevice *t ) : + Q3HttpNormalRequest( h, d, t ) + { } + + ~Q3HttpPGHRequest() + { } + + void start( Q3Http * ); +}; + +void Q3HttpPGHRequest::start( Q3Http *http ) +{ + header.setValue( QLatin1String("Host"), http->d->hostname ); + Q3HttpNormalRequest::start( http ); +} + +/**************************************************** + * + * Q3HttpSetHostRequest + * + ****************************************************/ + +class Q3HttpSetHostRequest : public Q3HttpRequest +{ +public: + Q3HttpSetHostRequest( const QString &h, Q_UINT16 p ) : + hostname(h), port(p) + { } + + void start( Q3Http * ); + + QIODevice* sourceDevice() + { return 0; } + QIODevice* destinationDevice() + { return 0; } + +private: + QString hostname; + Q_UINT16 port; +}; + +void Q3HttpSetHostRequest::start( Q3Http *http ) +{ + http->d->hostname = hostname; + http->d->port = port; + http->finishedWithSuccess(); +} + +/**************************************************** + * + * Q3HttpCloseRequest + * + ****************************************************/ + +class Q3HttpCloseRequest : public Q3HttpRequest +{ +public: + Q3HttpCloseRequest() + { } + void start( Q3Http * ); + + QIODevice* sourceDevice() + { return 0; } + QIODevice* destinationDevice() + { return 0; } +}; + +void Q3HttpCloseRequest::start( Q3Http *http ) +{ + http->close(); +} + +/**************************************************** + * + * Q3HttpHeader + * + ****************************************************/ + +/*! + \class Q3HttpHeader + \brief The Q3HttpHeader class contains header information for HTTP. + + \compat + + In most cases you should use the more specialized derivatives of + this class, Q3HttpResponseHeader and Q3HttpRequestHeader, rather + than directly using Q3HttpHeader. + + Q3HttpHeader provides the HTTP header fields. A HTTP header field + consists of a name followed by a colon, a single space, and the + field value. (See RFC 1945.) Field names are case-insensitive. A + typical header field looks like this: + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 0 + + In the API the header field name is called the "key" and the + content is called the "value". You can get and set a header + field's value by using its key with value() and setValue(), e.g. + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 1 + + Some fields are so common that getters and setters are provided + for them as a convenient alternative to using \l value() and + \l setValue(), e.g. contentLength() and contentType(), + setContentLength() and setContentType(). + + Each header key has a \e single value associated with it. If you + set the value for a key which already exists the previous value + will be discarded. + + \sa Q3HttpRequestHeader Q3HttpResponseHeader +*/ + +/*! + \fn int Q3HttpHeader::majorVersion() const + + Returns the major protocol-version of the HTTP header. +*/ + +/*! + \fn int Q3HttpHeader::minorVersion() const + + Returns the minor protocol-version of the HTTP header. +*/ + +/*! + Constructs an empty HTTP header. +*/ +Q3HttpHeader::Q3HttpHeader() + : valid( true ) +{ +} + +/*! + Constructs a copy of \a header. +*/ +Q3HttpHeader::Q3HttpHeader( const Q3HttpHeader& header ) + : valid( header.valid ) +{ + values = header.values; +} + +/*! + Constructs a HTTP header for \a str. + + This constructor parses the string \a str for header fields and + adds this information. The \a str should consist of one or more + "\r\n" delimited lines; each of these lines should have the format + key, colon, space, value. +*/ +Q3HttpHeader::Q3HttpHeader( const QString& str ) + : valid( true ) +{ + parse( str ); +} + +/*! + Destructor. +*/ +Q3HttpHeader::~Q3HttpHeader() +{ +} + +/*! + Assigns \a h and returns a reference to this http header. +*/ +Q3HttpHeader& Q3HttpHeader::operator=( const Q3HttpHeader& h ) +{ + values = h.values; + valid = h.valid; + return *this; +} + +/*! + Returns true if the HTTP header is valid; otherwise returns false. + + A Q3HttpHeader is invalid if it was created by parsing a malformed string. +*/ +bool Q3HttpHeader::isValid() const +{ + return valid; +} + +/*! \internal + Parses the HTTP header string \a str for header fields and adds + the keys/values it finds. If the string is not parsed successfully + the Q3HttpHeader becomes \link isValid() invalid\endlink. + + Returns true if \a str was successfully parsed; otherwise returns false. + + \sa toString() +*/ +bool Q3HttpHeader::parse( const QString& str ) +{ + QStringList lst; + int pos = str.find( QLatin1Char('\n') ); + if ( pos > 0 && str.at( pos - 1 ) == QLatin1Char('\r') ) + lst = QStringList::split( QLatin1String("\r\n"), str.stripWhiteSpace(), false ); + else + lst = QStringList::split( QLatin1String("\n"), str.stripWhiteSpace(), false ); + + if ( lst.isEmpty() ) + return true; + + QStringList lines; + QStringList::Iterator it = lst.begin(); + for( ; it != lst.end(); ++it ) { + if ( !(*it).isEmpty() ) { + if ( (*it)[0].isSpace() ) { + if ( !lines.isEmpty() ) { + lines.last() += QLatin1String(" "); + lines.last() += (*it).stripWhiteSpace(); + } + } else { + lines.append( (*it) ); + } + } + } + + int number = 0; + it = lines.begin(); + for( ; it != lines.end(); ++it ) { + if ( !parseLine( *it, number++ ) ) { + valid = false; + return false; + } + } + return true; +} + +/*! \internal +*/ +void Q3HttpHeader::setValid( bool v ) +{ + valid = v; +} + +/*! + Returns the value for the entry with the given \a key. If no entry + has this \a key, an empty string is returned. + + \sa setValue() removeValue() hasKey() keys() +*/ +QString Q3HttpHeader::value( const QString& key ) const +{ + return values[ key.lower() ]; +} + +/*! + Returns a list of the keys in the HTTP header. + + \sa hasKey() +*/ +QStringList Q3HttpHeader::keys() const +{ + return values.keys(); +} + +/*! + Returns true if the HTTP header has an entry with the given \a + key; otherwise returns false. + + \sa value() setValue() keys() +*/ +bool Q3HttpHeader::hasKey( const QString& key ) const +{ + return values.contains( key.lower() ); +} + +/*! + Sets the value of the entry with the \a key to \a value. + + If no entry with \a key exists, a new entry with the given \a key + and \a value is created. If an entry with the \a key already + exists, its value is discarded and replaced with the given \a + value. + + \sa value() hasKey() removeValue() +*/ +void Q3HttpHeader::setValue( const QString& key, const QString& value ) +{ + values[ key.lower() ] = value; +} + +/*! + Removes the entry with the key \a key from the HTTP header. + + \sa value() setValue() +*/ +void Q3HttpHeader::removeValue( const QString& key ) +{ + values.remove( key.lower() ); +} + +/*! \internal + Parses the single HTTP header line \a line which has the format + key, colon, space, value, and adds key/value to the headers. The + linenumber is \a number. Returns true if the line was successfully + parsed and the key/value added; otherwise returns false. + + \sa parse() +*/ +bool Q3HttpHeader::parseLine( const QString& line, int ) +{ + int i = line.find( QLatin1String(":") ); + if ( i == -1 ) + return false; + + values.insert( line.left( i ).stripWhiteSpace().lower(), line.mid( i + 1 ).stripWhiteSpace() ); + + return true; +} + +/*! + Returns a string representation of the HTTP header. + + The string is suitable for use by the constructor that takes a + QString. It consists of lines with the format: key, colon, space, + value, "\r\n". +*/ +QString Q3HttpHeader::toString() const +{ + if ( !isValid() ) + return QLatin1String(""); + + QString ret = QLatin1String(""); + + QMap<QString,QString>::ConstIterator it = values.begin(); + for( ; it != values.end(); ++it ) + ret += it.key() + QLatin1String(": ") + it.data() + QLatin1String("\r\n"); + + return ret; +} + +/*! + Returns true if the header has an entry for the special HTTP + header field \c content-length; otherwise returns false. + + \sa contentLength() setContentLength() +*/ +bool Q3HttpHeader::hasContentLength() const +{ + return hasKey( QLatin1String("content-length") ); +} + +/*! + Returns the value of the special HTTP header field \c + content-length. + + \sa setContentLength() hasContentLength() +*/ +uint Q3HttpHeader::contentLength() const +{ + return values[ QLatin1String("content-length") ].toUInt(); +} + +/*! + Sets the value of the special HTTP header field \c content-length + to \a len. + + \sa contentLength() hasContentLength() +*/ +void Q3HttpHeader::setContentLength( int len ) +{ + values[ QLatin1String("content-length") ] = QString::number( len ); +} + +/*! + Returns true if the header has an entry for the the special HTTP + header field \c content-type; otherwise returns false. + + \sa contentType() setContentType() +*/ +bool Q3HttpHeader::hasContentType() const +{ + return hasKey( QLatin1String("content-type") ); +} + +/*! + Returns the value of the special HTTP header field \c content-type. + + \sa setContentType() hasContentType() +*/ +QString Q3HttpHeader::contentType() const +{ + QString type = values[ QLatin1String("content-type") ]; + if ( type.isEmpty() ) + return QString(); + + int pos = type.find( QLatin1String(";") ); + if ( pos == -1 ) + return type; + + return type.left( pos ).stripWhiteSpace(); +} + +/*! + Sets the value of the special HTTP header field \c content-type to + \a type. + + \sa contentType() hasContentType() +*/ +void Q3HttpHeader::setContentType( const QString& type ) +{ + values[ QLatin1String("content-type") ] = type; +} + +/**************************************************** + * + * Q3HttpResponseHeader + * + ****************************************************/ + +/*! + \class Q3HttpResponseHeader + \brief The Q3HttpResponseHeader class contains response header information for HTTP. + + \compat + + This class is used by the Q3Http class to report the header + information that the client received from the server. + + HTTP responses have a status code that indicates the status of the + response. This code is a 3-digit integer result code (for details + see to RFC 1945). In addition to the status code, you can also + specify a human-readable text that describes the reason for the + code ("reason phrase"). This class allows you to get the status + code and the reason phrase. + + \sa Q3HttpRequestHeader Q3Http +*/ + +/*! + Constructs an empty HTTP response header. +*/ +Q3HttpResponseHeader::Q3HttpResponseHeader() +{ + setValid( false ); +} + +/*! + Constructs a HTTP response header with the status code \a code, + the reason phrase \a text and the protocol-version \a majorVer and + \a minorVer. +*/ +Q3HttpResponseHeader::Q3HttpResponseHeader( int code, const QString& text, int majorVer, int minorVer ) + : Q3HttpHeader(), statCode( code ), reasonPhr( text ), majVer( majorVer ), minVer( minorVer ) +{ +} + +/*! + Constructs a copy of \a header. +*/ +Q3HttpResponseHeader::Q3HttpResponseHeader( const Q3HttpResponseHeader& header ) + : Q3HttpHeader( header ), statCode( header.statCode ), reasonPhr( header.reasonPhr ), majVer( header.majVer ), minVer( header.minVer ) +{ +} + +/*! + Constructs a HTTP response header from the string \a str. The + string is parsed and the information is set. The \a str should + consist of one or more "\r\n" delimited lines; the first line should be the + status-line (format: HTTP-version, space, status-code, space, + reason-phrase); each of remaining lines should have the format key, colon, + space, value. +*/ +Q3HttpResponseHeader::Q3HttpResponseHeader( const QString& str ) + : Q3HttpHeader() +{ + parse( str ); +} + +/*! + Sets the status code to \a code, the reason phrase to \a text and + the protocol-version to \a majorVer and \a minorVer. + + \sa statusCode() reasonPhrase() majorVersion() minorVersion() +*/ +void Q3HttpResponseHeader::setStatusLine( int code, const QString& text, int majorVer, int minorVer ) +{ + setValid( true ); + statCode = code; + reasonPhr = text; + majVer = majorVer; + minVer = minorVer; +} + +/*! + Returns the status code of the HTTP response header. + + \sa reasonPhrase() majorVersion() minorVersion() +*/ +int Q3HttpResponseHeader::statusCode() const +{ + return statCode; +} + +/*! + Returns the reason phrase of the HTTP response header. + + \sa statusCode() majorVersion() minorVersion() +*/ +QString Q3HttpResponseHeader::reasonPhrase() const +{ + return reasonPhr; +} + +/*! + Returns the major protocol-version of the HTTP response header. + + \sa minorVersion() statusCode() reasonPhrase() +*/ +int Q3HttpResponseHeader::majorVersion() const +{ + return majVer; +} + +/*! + Returns the minor protocol-version of the HTTP response header. + + \sa majorVersion() statusCode() reasonPhrase() +*/ +int Q3HttpResponseHeader::minorVersion() const +{ + return minVer; +} + +/*! \reimp +*/ +bool Q3HttpResponseHeader::parseLine( const QString& line, int number ) +{ + if ( number != 0 ) + return Q3HttpHeader::parseLine( line, number ); + + QString l = line.simplifyWhiteSpace(); + if ( l.length() < 10 ) + return false; + + if ( l.left( 5 ) == QLatin1String("HTTP/") && l[5].isDigit() && l[6] == QLatin1Char('.') && + l[7].isDigit() && l[8] == QLatin1Char(' ') && l[9].isDigit() ) { + majVer = l[5].latin1() - '0'; + minVer = l[7].latin1() - '0'; + + int pos = l.find( QLatin1Char(' '), 9 ); + if ( pos != -1 ) { + reasonPhr = l.mid( pos + 1 ); + statCode = l.mid( 9, pos - 9 ).toInt(); + } else { + statCode = l.mid( 9 ).toInt(); + reasonPhr.clear(); + } + } else { + return false; + } + + return true; +} + +/*! \reimp +*/ +QString Q3HttpResponseHeader::toString() const +{ + QString ret( QLatin1String("HTTP/%1.%2 %3 %4\r\n%5\r\n") ); + return ret.arg( majVer ).arg ( minVer ).arg( statCode ).arg( reasonPhr ).arg( Q3HttpHeader::toString() ); +} + +/**************************************************** + * + * Q3HttpRequestHeader + * + ****************************************************/ + +/*! + \class Q3HttpRequestHeader + \brief The Q3HttpRequestHeader class contains request header information for + HTTP. + + \compat + + This class is used in the Q3Http class to report the header + information if the client requests something from the server. + + HTTP requests have a method which describes the request's action. + The most common requests are "GET" and "POST". In addition to the + request method the header also includes a request-URI to specify + the location for the method to use. + + The method, request-URI and protocol-version can be set using a + constructor or later using setRequest(). The values can be + obtained using method(), path(), majorVersion() and + minorVersion(). + + This class is a Q3HttpHeader subclass so that class's functions, + e.g. \link Q3HttpHeader::setValue() setValue()\endlink, \link + Q3HttpHeader::value() value()\endlink, etc. are also available. + + \sa Q3HttpResponseHeader Q3Http +*/ + +/*! + Constructs an empty HTTP request header. +*/ +Q3HttpRequestHeader::Q3HttpRequestHeader() + : Q3HttpHeader() +{ + setValid( false ); +} + +/*! + Constructs a HTTP request header for the method \a method, the + request-URI \a path and the protocol-version \a majorVer and \a minorVer. +*/ +Q3HttpRequestHeader::Q3HttpRequestHeader( const QString& method, const QString& path, int majorVer, int minorVer ) + : Q3HttpHeader(), m( method ), p( path ), majVer( majorVer ), minVer( minorVer ) +{ +} + +/*! + Constructs a copy of \a header. +*/ +Q3HttpRequestHeader::Q3HttpRequestHeader( const Q3HttpRequestHeader& header ) + : Q3HttpHeader( header ), m( header.m ), p( header.p ), majVer( header.majVer ), minVer( header.minVer ) +{ +} + +/*! + Constructs a HTTP request header from the string \a str. The \a + str should consist of one or more "\r\n" delimited lines; the first line + should be the request-line (format: method, space, request-URI, space + HTTP-version); each of the remaining lines should have the format key, + colon, space, value. +*/ +Q3HttpRequestHeader::Q3HttpRequestHeader( const QString& str ) + : Q3HttpHeader() +{ + parse( str ); +} + +/*! + This function sets the request method to \a method, the + request-URI to \a path and the protocol-version to \a majorVer and + \a minorVer. + + \sa method() path() majorVersion() minorVersion() +*/ +void Q3HttpRequestHeader::setRequest( const QString& method, const QString& path, int majorVer, int minorVer ) +{ + setValid( true ); + m = method; + p = path; + majVer = majorVer; + minVer = minorVer; +} + +/*! + Returns the method of the HTTP request header. + + \sa path() majorVersion() minorVersion() setRequest() +*/ +QString Q3HttpRequestHeader::method() const +{ + return m; +} + +/*! + Returns the request-URI of the HTTP request header. + + \sa method() majorVersion() minorVersion() setRequest() +*/ +QString Q3HttpRequestHeader::path() const +{ + return p; +} + +/*! + Returns the major protocol-version of the HTTP request header. + + \sa minorVersion() method() path() setRequest() +*/ +int Q3HttpRequestHeader::majorVersion() const +{ + return majVer; +} + +/*! + Returns the minor protocol-version of the HTTP request header. + + \sa majorVersion() method() path() setRequest() +*/ +int Q3HttpRequestHeader::minorVersion() const +{ + return minVer; +} + +/*! \reimp +*/ +bool Q3HttpRequestHeader::parseLine( const QString& line, int number ) +{ + if ( number != 0 ) + return Q3HttpHeader::parseLine( line, number ); + + QStringList lst = QStringList::split( QLatin1String(" "), line.simplifyWhiteSpace() ); + if ( lst.count() > 0 ) { + m = lst[0]; + if ( lst.count() > 1 ) { + p = lst[1]; + if ( lst.count() > 2 ) { + QString v = lst[2]; + if ( v.length() >= 8 && v.left( 5 ) == QLatin1String("HTTP/") && + v[5].isDigit() && v[6] == QLatin1Char('.') && v[7].isDigit() ) { + majVer = v[5].latin1() - '0'; + minVer = v[7].latin1() - '0'; + return true; + } + } + } + } + + return false; +} + +/*! \reimp +*/ +QString Q3HttpRequestHeader::toString() const +{ + QString first( QLatin1String("%1 %2")); + QString last(QLatin1String(" HTTP/%3.%4\r\n%5\r\n") ); + return first.arg( m ).arg( p ) + + last.arg( majVer ).arg( minVer ).arg( Q3HttpHeader::toString()); +} + + +/**************************************************** + * + * Q3Http + * + ****************************************************/ +/*! + \class Q3Http + \brief The Q3Http class provides an implementation of the HTTP protocol. + + \compat + + This class provides two different interfaces: one is the + Q3NetworkProtocol interface that allows you to use HTTP through the + QUrlOperator abstraction. The other is a direct interface to HTTP + that allows you to have more control over the requests and that + allows you to access the response header fields. + + Don't mix the two interfaces, since the behavior is not + well-defined. + + If you want to use Q3Http with the Q3NetworkProtocol interface, you + do not use it directly, but rather through a QUrlOperator, for + example: + + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 2 + + This code will only work if the Q3Http class is registered; to + register the class, you must call q3InitNetworkProtocols() before + using a QUrlOperator with HTTP. + + The Q3NetworkProtocol interface for HTTP only supports the + operations operationGet() and operationPut(), i.e. + QUrlOperator::get() and QUrlOperator::put(), if you use it with a + QUrlOperator. + + The rest of this descrption describes the direct interface to + HTTP. + + The class works asynchronously, so there are no blocking + functions. If an operation cannot be executed immediately, the + function will still return straight away and the operation will be + scheduled for later execution. The results of scheduled operations + are reported via signals. This approach depends on the event loop + being in operation. + + The operations that can be scheduled (they are called "requests" + in the rest of the documentation) are the following: setHost(), + get(), post(), head() and request(). + + All of these requests return a unique identifier that allows you + to keep track of the request that is currently executed. When the + execution of a request starts, the requestStarted() signal with + the identifier is emitted and when the request is finished, the + requestFinished() signal is emitted with the identifier and a bool + that indicates if the request finished with an error. + + To make an HTTP request you must set up suitable HTTP headers. The + following example demonstrates, how to request the main HTML page + from the Trolltech home page (i.e. the URL + http://qtsoftware.com/index.html): + + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 3 + + For the common HTTP requests \c GET, \c POST and \c HEAD, Q3Http + provides the convenience functions get(), post() and head(). They + already use a reasonable header and if you don't have to set + special header fields, they are easier to use. The above example + can also be written as: + + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 4 + + For this example the following sequence of signals is emitted + (with small variations, depending on network traffic, etc.): + + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 5 + + The dataSendProgress() and dataReadProgress() signals in the above + example are useful if you want to show a \link QProgressBar + progress bar\endlink to inform the user about the progress of the + download. The second argument is the total size of data. In + certain cases it is not possible to know the total amount in + advance, in which case the second argument is 0. (If you connect + to a QProgressBar a total of 0 results in a busy indicator.) + + When the response header is read, it is reported with the + responseHeaderReceived() signal. + + The readyRead() signal tells you that there is data ready to be + read. The amount of data can then be queried with the + bytesAvailable() function and it can be read with the readBlock() + or readAll() functions. + + If an error occurs during the execution of one of the commands in + a sequence of commands, all the pending commands (i.e. scheduled, + but not yet executed commands) are cleared and no signals are + emitted for them. + + For example, if you have the following sequence of reqeusts + + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 6 + + and the get() request fails because the host lookup fails, then + the post() request is never executed and the signals would look + like this: + + \snippet doc/src/snippets/code/src_qt3support_network_q3http.cpp 7 + + You can then get details about the error with the error() and + errorString() functions. Note that only unexpected behaviour, like + network failure is considered as an error. If the server response + contains an error status, like a 404 response, this is reported as + a normal response case. So you should always check the \link + Q3HttpResponseHeader::statusCode() status code \endlink of the + response header. + + The functions currentId() and currentRequest() provide more + information about the currently executing request. + + The functions hasPendingRequests() and clearPendingRequests() + allow you to query and clear the list of pending requests. + + \sa Q3NetworkProtocol, Q3UrlOperator, Q3Ftp +*/ + +/*! + Constructs a Q3Http object. +*/ +Q3Http::Q3Http() +{ + init(); +} + +/*! + Constructs a Q3Http object. The parameters \a parent and \a name + are passed on to the QObject constructor. +*/ +Q3Http::Q3Http( QObject* parent, const char* name ) +{ + if ( parent ) + parent->insertChild( this ); + setName( name ); + init(); +} + +/*! + Constructs a Q3Http object. Subsequent requests are done by + connecting to the server \a hostname on port \a port. The + parameters \a parent and \a name are passed on to the QObject + constructor. + + \sa setHost() +*/ +Q3Http::Q3Http( const QString &hostname, Q_UINT16 port, QObject* parent, const char* name ) +{ + if ( parent ) + parent->insertChild( this ); + setName( name ); + init(); + + d->hostname = hostname; + d->port = port; +} + +void Q3Http::init() +{ + bytesRead = 0; + d = new Q3HttpPrivate; + d->errorString = QHttp::tr( "Unknown error" ); + + connect( &d->socket, SIGNAL(connected()), + this, SLOT(slotConnected()) ); + connect( &d->socket, SIGNAL(connectionClosed()), + this, SLOT(slotClosed()) ); + connect( &d->socket, SIGNAL(delayedCloseFinished()), + this, SLOT(slotClosed()) ); + connect( &d->socket, SIGNAL(readyRead()), + this, SLOT(slotReadyRead()) ); + connect( &d->socket, SIGNAL(error(int)), + this, SLOT(slotError(int)) ); + connect( &d->socket, SIGNAL(bytesWritten(int)), + this, SLOT(slotBytesWritten(int)) ); + + d->idleTimer = startTimer( 0 ); +} + +/*! + Destroys the Q3Http object. If there is an open connection, it is + closed. +*/ +Q3Http::~Q3Http() +{ + abort(); + delete d; +} + +/*! + \enum Q3Http::State + + This enum is used to specify the state the client is in: + + \value Unconnected There is no connection to the host. + \value HostLookup A host name lookup is in progress. + \value Connecting An attempt to connect to the host is in progress. + \value Sending The client is sending its request to the server. + \value Reading The client's request has been sent and the client + is reading the server's response. + \value Connected The connection to the host is open, but the client is + neither sending a request, nor waiting for a response. + \value Closing The connection is closing down, but is not yet + closed. (The state will be \c Unconnected when the connection is + closed.) + + \sa stateChanged() state() +*/ + +/*! \enum Q3Http::Error + + This enum identifies the error that occurred. + + \value NoError No error occurred. + \value HostNotFound The host name lookup failed. + \value ConnectionRefused The server refused the connection. + \value UnexpectedClose The server closed the connection unexpectedly. + \value InvalidResponseHeader The server sent an invalid response header. + \value WrongContentLength The client could not read the content correctly + because an error with respect to the content length occurred. + \value Aborted The request was aborted with abort(). + \value UnknownError An error other than those specified above + occurred. + + \sa error() +*/ + +/*! + \fn void Q3Http::stateChanged( int state ) + + This signal is emitted when the state of the Q3Http object changes. + The argument \a state is the new state of the connection; it is + one of the \l State values. + + This usually happens when a request is started, but it can also + happen when the server closes the connection or when a call to + closeConnection() succeeded. + + \sa get() post() head() request() closeConnection() state() State +*/ + +/*! + \fn void Q3Http::responseHeaderReceived( const Q3HttpResponseHeader& resp ) + + This signal is emitted when the HTTP header of a server response + is available. The header is passed in \a resp. + + \sa get() post() head() request() readyRead() +*/ + +/*! + \fn void Q3Http::readyRead( const Q3HttpResponseHeader& resp ) + + This signal is emitted when there is new response data to read. + + If you specified a device in the request where the data should be + written to, then this signal is \e not emitted; instead the data + is written directly to the device. + + The response header is passed in \a resp. + + You can read the data with the readAll() or readBlock() functions + + This signal is useful if you want to process the data in chunks as + soon as it becomes available. If you are only interested in the + complete data, just connect to the requestFinished() signal and + read the data then instead. + + \sa get() post() request() readAll() readBlock() bytesAvailable() +*/ + +/*! + \fn void Q3Http::dataSendProgress( int done, int total ) + + This signal is emitted when this object sends data to a HTTP + server to inform it about the progress of the upload. + + \a done is the amount of data that has already arrived and \a + total is the total amount of data. It is possible that the total + amount of data that should be transferred cannot be determined, in + which case \a total is 0.(If you connect to a QProgressBar, the + progress bar shows a busy indicator if the total is 0). + + \warning \a done and \a total are not necessarily the size in + bytes, since for large files these values might need to be + "scaled" to avoid overflow. + + \sa dataReadProgress() post() request() QProgressBar::setValue() +*/ + +/*! + \fn void Q3Http::dataReadProgress( int done, int total ) + + This signal is emitted when this object reads data from a HTTP + server to indicate the current progress of the download. + + \a done is the amount of data that has already arrived and \a + total is the total amount of data. It is possible that the total + amount of data that should be transferred cannot be determined, in + which case \a total is 0.(If you connect to a QProgressBar, the + progress bar shows a busy indicator if the total is 0). + + \warning \a done and \a total are not necessarily the size in + bytes, since for large files these values might need to be + "scaled" to avoid overflow. + + \sa dataSendProgress() get() post() request() QProgressBar::setValue() +*/ + +/*! + \fn void Q3Http::requestStarted( int id ) + + This signal is emitted when processing the request identified by + \a id starts. + + \sa requestFinished() done() +*/ + +/*! + \fn void Q3Http::requestFinished( int id, bool error ) + + This signal is emitted when processing the request identified by + \a id has finished. \a error is true if an error occurred during + the processing; otherwise \a error is false. + + \sa requestStarted() done() error() errorString() +*/ + +/*! + \fn void Q3Http::done( bool error ) + + This signal is emitted when the last pending request has finished; + (it is emitted after the last request's requestFinished() signal). + \a error is true if an error occurred during the processing; + otherwise \a error is false. + + \sa requestFinished() error() errorString() +*/ + +/*! + Aborts the current request and deletes all scheduled requests. + + For the current request, the requestFinished() signal with the \c + error argument \c true is emitted. For all other requests that are + affected by the abort(), no signals are emitted. + + Since this slot also deletes the scheduled requests, there are no + requests left and the done() signal is emitted (with the \c error + argument \c true). + + \sa clearPendingRequests() +*/ +void Q3Http::abort() +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return; + + finishedWithError( QHttp::tr("Request aborted"), Aborted ); + clearPendingRequests(); + d->socket.clearPendingData(); + close(); +} + +/*! + Returns the number of bytes that can be read from the response + content at the moment. + + \sa get() post() request() readyRead() readBlock() readAll() +*/ +Q_ULONG Q3Http::bytesAvailable() const +{ +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http::bytesAvailable(): %d bytes", (int)d->rba.size() ); +#endif + return d->rba.size(); +} + +/*! + Reads \a maxlen bytes from the response content into \a data and + returns the number of bytes read. Returns -1 if an error occurred. + + \sa get() post() request() readyRead() bytesAvailable() readAll() +*/ +Q_LONG Q3Http::readBlock( char *data, Q_ULONG maxlen ) +{ + if ( data == 0 && maxlen != 0 ) { +#if defined(QT_CHECK_NULL) + qWarning( "Q3Http::readBlock: Null pointer error" ); +#endif + return -1; + } + if ( maxlen >= (Q_ULONG)d->rba.size() ) + maxlen = d->rba.size(); + d->rba.consumeBytes( maxlen, data ); + + d->bytesDone += maxlen; +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http::readBlock(): read %d bytes (%d bytes done)", (int)maxlen, d->bytesDone ); +#endif + return maxlen; +} + +/*! + Reads all the bytes from the response content and returns them. + + \sa get() post() request() readyRead() bytesAvailable() readBlock() +*/ +QByteArray Q3Http::readAll() +{ + Q_ULONG avail = bytesAvailable(); + QByteArray tmp( avail ); + Q_LONG read = readBlock( tmp.data(), avail ); + tmp.resize( read ); + return tmp; +} + +/*! + Returns the identifier of the HTTP request being executed or 0 if + there is no request being executed (i.e. they've all finished). + + \sa currentRequest() +*/ +int Q3Http::currentId() const +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return 0; + return r->id; +} + +/*! + Returns the request header of the HTTP request being executed. If + the request is one issued by setHost() or closeConnection(), it + returns an invalid request header, i.e. + Q3HttpRequestHeader::isValid() returns false. + + \sa currentId() +*/ +Q3HttpRequestHeader Q3Http::currentRequest() const +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( r != 0 && r->hasRequestHeader() ) + return r->requestHeader(); + return Q3HttpRequestHeader(); +} + +/*! + Returns the QIODevice pointer that is used as the data source of the HTTP + request being executed. If there is no current request or if the request + does not use an IO device as the data source, this function returns 0. + + This function can be used to delete the QIODevice in the slot connected to + the requestFinished() signal. + + \sa currentDestinationDevice() post() request() +*/ +QIODevice* Q3Http::currentSourceDevice() const +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( !r ) + return 0; + return r->sourceDevice(); +} + +/*! + Returns the QIODevice pointer that is used as to store the data of the HTTP + request being executed. If there is no current request or if the request + does not store the data to an IO device, this function returns 0. + + This function can be used to delete the QIODevice in the slot connected to + the requestFinished() signal. + + \sa get() post() request() +*/ +QIODevice* Q3Http::currentDestinationDevice() const +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( !r ) + return 0; + return r->destinationDevice(); +} + +/*! + Returns true if there are any requests scheduled that have not yet + been executed; otherwise returns false. + + The request that is being executed is \e not considered as a + scheduled request. + + \sa clearPendingRequests() currentId() currentRequest() +*/ +bool Q3Http::hasPendingRequests() const +{ + return d->pending.count() > 1; +} + +/*! + Deletes all pending requests from the list of scheduled requests. + This does not affect the request that is being executed. If + you want to stop this as well, use abort(). + + \sa hasPendingRequests() abort() +*/ +void Q3Http::clearPendingRequests() +{ + Q3HttpRequest *r = 0; + if ( d->pending.count() > 0 ) + r = d->pending.take( 0 ); + d->pending.clear(); + if ( r ) + d->pending.append( r ); +} + +/*! + Sets the HTTP server that is used for requests to \a hostname on + port \a port. + + The function does not block and returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa get() post() head() request() requestStarted() requestFinished() done() +*/ +int Q3Http::setHost(const QString &hostname, Q_UINT16 port ) +{ + return addRequest( new Q3HttpSetHostRequest( hostname, port ) ); +} + +/*! + Sends a get request for \a path to the server set by setHost() or + as specified in the constructor. + + \a path must be an absolute path like \c /index.html or an + absolute URI like \c http://qtsoftware.com/index.html. + + If the IO device \a to is 0 the readyRead() signal is emitted + every time new content data is available to read. + + If the IO device \a to is not 0, the content data of the response + is written directly to the device. Make sure that the \a to + pointer is valid for the duration of the operation (it is safe to + delete it when the requestFinished() signal is emitted). + + The function does not block and returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() post() head() request() requestStarted() requestFinished() done() +*/ +int Q3Http::get( const QString& path, QIODevice* to ) +{ + Q3HttpRequestHeader header( QLatin1String("GET"), path ); + header.setValue( QLatin1String("Connection"), QLatin1String("Keep-Alive") ); + return addRequest( new Q3HttpPGHRequest( header, (QIODevice*)0, to ) ); +} + +/*! + Sends a post request for \a path to the server set by setHost() or + as specified in the constructor. + + \a path must be an absolute path like \c /index.html or an + absolute URI like \c http://qtsoftware.com/index.html. + + The incoming data comes via the \a data IO device. + + If the IO device \a to is 0 the readyRead() signal is emitted + every time new content data is available to read. + + If the IO device \a to is not 0, the content data of the response + is written directly to the device. Make sure that the \a to + pointer is valid for the duration of the operation (it is safe to + delete it when the requestFinished() signal is emitted). + + The function does not block and returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() get() head() request() requestStarted() requestFinished() done() +*/ +int Q3Http::post( const QString& path, QIODevice* data, QIODevice* to ) +{ + Q3HttpRequestHeader header( QLatin1String("POST"), path ); + header.setValue( QLatin1String("Connection"), QLatin1String("Keep-Alive") ); + return addRequest( new Q3HttpPGHRequest( header, data, to ) ); +} + +/*! + \overload + + \a data is used as the content data of the HTTP request. +*/ +int Q3Http::post( const QString& path, const QByteArray& data, QIODevice* to ) +{ + Q3HttpRequestHeader header( QLatin1String("POST"), path ); + header.setValue( QLatin1String("Connection"), QLatin1String("Keep-Alive") ); + return addRequest( new Q3HttpPGHRequest( header, new QByteArray(data), to ) ); +} + +/*! + Sends a header request for \a path to the server set by setHost() + or as specified in the constructor. + + \a path must be an absolute path like \c /index.html or an + absolute URI like \c http://qtsoftware.com/index.html. + + The function does not block and returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() get() post() request() requestStarted() requestFinished() done() +*/ +int Q3Http::head( const QString& path ) +{ + Q3HttpRequestHeader header( QLatin1String("HEAD"), path ); + header.setValue( QLatin1String("Connection"), QLatin1String("Keep-Alive") ); + return addRequest( new Q3HttpPGHRequest( header, (QIODevice*)0, 0 ) ); +} + +/*! + Sends a request to the server set by setHost() or as specified in + the constructor. Uses the \a header as the HTTP request header. + You are responsible for setting up a header that is appropriate + for your request. + + The incoming data comes via the \a data IO device. + + If the IO device \a to is 0 the readyRead() signal is emitted + every time new content data is available to read. + + If the IO device \a to is not 0, the content data of the response + is written directly to the device. Make sure that the \a to + pointer is valid for the duration of the operation (it is safe to + delete it when the requestFinished() signal is emitted). + + The function does not block and returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + \sa setHost() get() post() head() requestStarted() requestFinished() done() +*/ +int Q3Http::request( const Q3HttpRequestHeader &header, QIODevice *data, QIODevice *to ) +{ + return addRequest( new Q3HttpNormalRequest( header, data, to ) ); +} + +/*! + \overload + + \a data is used as the content data of the HTTP request. +*/ +int Q3Http::request( const Q3HttpRequestHeader &header, const QByteArray &data, QIODevice *to ) +{ + return addRequest( new Q3HttpNormalRequest( header, new QByteArray(data), to ) ); +} + +/*! + Closes the connection; this is useful if you have a keep-alive + connection and want to close it. + + For the requests issued with get(), post() and head(), Q3Http sets + the connection to be keep-alive. You can also do this using the + header you pass to the request() function. Q3Http only closes the + connection to the HTTP server if the response header requires it + to do so. + + The function does not block and returns immediately. The request + is scheduled, and its execution is performed asynchronously. The + function returns a unique identifier which is passed by + requestStarted() and requestFinished(). + + When the request is started the requestStarted() signal is + emitted. When it is finished the requestFinished() signal is + emitted. + + If you want to close the connection immediately, you have to use + abort() instead. + + \sa stateChanged() abort() requestStarted() requestFinished() done() +*/ +int Q3Http::closeConnection() +{ + return addRequest( new Q3HttpCloseRequest() ); +} + +int Q3Http::addRequest( Q3HttpRequest *req ) +{ + d->pending.append( req ); + + if ( d->pending.count() == 1 ) + // don't emit the requestStarted() signal before the id is returned + QTimer::singleShot( 0, this, SLOT(startNextRequest()) ); + + return req->id; +} + +void Q3Http::startNextRequest() +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return; + + d->error = NoError; + d->errorString = QHttp::tr( "Unknown error" ); + + if ( bytesAvailable() ) + readAll(); // clear the data + emit requestStarted( r->id ); + r->start( this ); +} + +void Q3Http::sendRequest() +{ + if ( d->hostname.isNull() ) { + finishedWithError( QHttp::tr("No server set to connect to"), UnknownError ); + return; + } + + killIdleTimer(); + + // Do we need to setup a new connection or can we reuse an + // existing one ? + if ( d->socket.peerName() != d->hostname || d->socket.peerPort() != d->port + || d->socket.state() != Q3Socket::Connection ) { + setState( Q3Http::Connecting ); + d->socket.connectToHost( d->hostname, d->port ); + } else { + slotConnected(); + } + +} + +void Q3Http::finishedWithSuccess() +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return; + + emit requestFinished( r->id, false ); + d->pending.removeFirst(); + if ( d->pending.isEmpty() ) { + emit done( false ); + } else { + startNextRequest(); + } +} + +void Q3Http::finishedWithError( const QString& detail, int errorCode ) +{ + Q3HttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return; + + d->error = (Error)errorCode; + d->errorString = detail; + emit requestFinished( r->id, true ); + + d->pending.clear(); + emit done( true ); +} + +void Q3Http::slotClosed() +{ + if ( d->state == Closing ) + return; + + if ( d->state == Reading ) { + if ( d->response.hasKey( QLatin1String("content-length") ) ) { + // We got Content-Length, so did we get all bytes? + if ( d->bytesDone+bytesAvailable() != d->response.contentLength() ) { + finishedWithError( QHttp::tr("Wrong content length"), WrongContentLength ); + } + } + } else if ( d->state == Connecting || d->state == Sending ) { + finishedWithError( QHttp::tr("Server closed connection unexpectedly"), UnexpectedClose ); + } + + d->postDevice = 0; + setState( Closing ); + d->idleTimer = startTimer( 0 ); +} + +void Q3Http::slotConnected() +{ + if ( d->state != Sending ) { + d->bytesDone = 0; + setState( Sending ); + } + + QString str = d->header.toString(); + d->bytesTotal = str.length(); + d->socket.writeBlock( str.latin1(), d->bytesTotal ); +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http: write request header:\n---{\n%s}---", str.latin1() ); +#endif + + if ( d->postDevice ) { + d->bytesTotal += d->postDevice->size(); + } else { + d->bytesTotal += d->buffer.size(); + d->socket.writeBlock( d->buffer.data(), d->buffer.size() ); + d->buffer = QByteArray(); // save memory + } +} + +void Q3Http::slotError( int err ) +{ + d->postDevice = 0; + + if ( d->state == Connecting || d->state == Reading || d->state == Sending ) { + switch ( err ) { + case Q3Socket::ErrConnectionRefused: + finishedWithError( QHttp::tr("Connection refused"), ConnectionRefused ); + break; + case Q3Socket::ErrHostNotFound: + finishedWithError( QHttp::tr("Host %1 not found").arg(d->socket.peerName()), HostNotFound ); + break; + default: + finishedWithError( QHttp::tr("HTTP request failed"), UnknownError ); + break; + } + } + + close(); +} + +void Q3Http::slotBytesWritten( int written ) +{ + d->bytesDone += written; + emit dataSendProgress( d->bytesDone, d->bytesTotal ); + + if ( !d->postDevice ) + return; + + if ( d->socket.bytesToWrite() == 0 ) { + int max = qMin<int>( 4096, d->postDevice->size() - d->postDevice->at() ); + QByteArray arr( max ); + + int n = d->postDevice->readBlock( arr.data(), max ); + if ( n != max ) { + qWarning("Could not read enough bytes from the device"); + close(); + return; + } + if ( d->postDevice->atEnd() ) { + d->postDevice = 0; + } + + d->socket.writeBlock( arr.data(), max ); + } +} + +void Q3Http::slotReadyRead() +{ + if ( d->state != Reading ) { + setState( Reading ); + d->buffer = QByteArray(); + d->readHeader = true; + d->headerStr = QLatin1String(""); + d->bytesDone = 0; + d->chunkedSize = -1; + } + + while ( d->readHeader ) { + bool end = false; + QString tmp; + while ( !end && d->socket.canReadLine() ) { + tmp = QLatin1String(d->socket.readLine()); + if ( tmp == QLatin1String("\r\n") || tmp == QLatin1String("\n") ) + end = true; + else + d->headerStr += tmp; + } + + if ( !end ) + return; + +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http: read response header:\n---{\n%s}---", d->headerStr.latin1() ); +#endif + d->response = Q3HttpResponseHeader( d->headerStr ); + d->headerStr = QLatin1String(""); +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http: read response header:\n---{\n%s}---", d->response.toString().latin1() ); +#endif + // Check header + if ( !d->response.isValid() ) { + finishedWithError( QHttp::tr("Invalid HTTP response header"), InvalidResponseHeader ); + close(); + return; + } + + // The 100-continue header is ignored, because when using the + // POST method, we send both the request header and data in + // one chunk. + if (d->response.statusCode() != 100) { + d->readHeader = false; + if ( d->response.hasKey( QLatin1String("transfer-encoding") ) && + d->response.value( QLatin1String("transfer-encoding") ).lower().contains( QLatin1String("chunked") ) ) + d->chunkedSize = 0; + + emit responseHeaderReceived( d->response ); + } + } + + if ( !d->readHeader ) { + bool everythingRead = false; + + if ( currentRequest().method() == QLatin1String("HEAD") ) { + everythingRead = true; + } else { + Q_ULONG n = d->socket.bytesAvailable(); + QByteArray *arr = 0; + if ( d->chunkedSize != -1 ) { + // transfer-encoding is chunked + for ( ;; ) { + // get chunk size + if ( d->chunkedSize == 0 ) { + if ( !d->socket.canReadLine() ) + break; + QString sizeString = QLatin1String(d->socket.readLine()); + int tPos = sizeString.find( QLatin1Char(';') ); + if ( tPos != -1 ) + sizeString.truncate( tPos ); + bool ok; + d->chunkedSize = sizeString.toInt( &ok, 16 ); + if ( !ok ) { + finishedWithError( QHttp::tr("Invalid HTTP chunked body"), WrongContentLength ); + close(); + delete arr; + return; + } + if ( d->chunkedSize == 0 ) // last-chunk + d->chunkedSize = -2; + } + + // read trailer + while ( d->chunkedSize == -2 && d->socket.canReadLine() ) { + QString read = QLatin1String(d->socket.readLine()); + if ( read == QLatin1String("\r\n") || read == QLatin1String("\n") ) + d->chunkedSize = -1; + } + if ( d->chunkedSize == -1 ) { + everythingRead = true; + break; + } + + // make sure that you can read the terminating CRLF, + // otherwise wait until next time... + n = d->socket.bytesAvailable(); + if ( n == 0 ) + break; + if ( (Q_LONG)n == d->chunkedSize || (Q_LONG)n == d->chunkedSize+1 ) { + n = d->chunkedSize - 1; + if ( n == 0 ) + break; + } + + // read data + uint toRead = QMIN( (Q_LONG)n, (d->chunkedSize < 0 ? (Q_LONG)n : d->chunkedSize) ); + if ( !arr ) + arr = new QByteArray( 0 ); + uint oldArrSize = arr->size(); + arr->resize( oldArrSize + toRead ); + Q_LONG read = d->socket.readBlock( arr->data()+oldArrSize, toRead ); + arr->resize( oldArrSize + read ); + + d->chunkedSize -= read; + + if ( d->chunkedSize == 0 && n - read >= 2 ) { + // read terminating CRLF + char tmp[2]; + d->socket.readBlock( tmp, 2 ); + if ( tmp[0] != '\r' || tmp[1] != '\n' ) { + finishedWithError( QHttp::tr("Invalid HTTP chunked body"), WrongContentLength ); + close(); + delete arr; + return; + } + } + } + } else if ( d->response.hasContentLength() ) { + n = qMin<ulong>( d->response.contentLength() - d->bytesDone, n ); + if ( n > 0 ) { + arr = new QByteArray( n ); + Q_LONG read = d->socket.readBlock( arr->data(), n ); + arr->resize( read ); + } + if ( d->bytesDone + bytesAvailable() + n == d->response.contentLength() ) + everythingRead = true; + } else if ( n > 0 ) { + // workaround for VC++ bug + QByteArray temp = d->socket.readAll(); + arr = new QByteArray( temp ); + } + + if ( arr ) { + n = arr->size(); + if ( d->toDevice ) { + d->toDevice->writeBlock( arr->data(), n ); + delete arr; + d->bytesDone += n; +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http::slotReadyRead(): read %ld bytes (%d bytes done)", n, d->bytesDone ); +#endif + if ( d->response.hasContentLength() ) + emit dataReadProgress( d->bytesDone, d->response.contentLength() ); + else + emit dataReadProgress( d->bytesDone, 0 ); + } else { + d->rba.append( arr ); +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http::slotReadyRead(): read %ld bytes (%ld bytes done)", n, d->bytesDone + bytesAvailable() ); +#endif + if ( d->response.hasContentLength() ) + emit dataReadProgress( d->bytesDone + bytesAvailable(), d->response.contentLength() ); + else + emit dataReadProgress( d->bytesDone + bytesAvailable(), 0 ); + emit readyRead( d->response ); + } + } + } + + if ( everythingRead ) { + // Handle "Connection: close" + if ( d->response.value(QLatin1String("connection")).lower() == QLatin1String("close") ) { + close(); + } else { + setState( Connected ); + // Start a timer, so that we emit the keep alive signal + // "after" this method returned. + d->idleTimer = startTimer( 0 ); + } + } + } +} + +/*! + Returns the current state of the object. When the state changes, + the stateChanged() signal is emitted. + + \sa State stateChanged() +*/ +Q3Http::State Q3Http::state() const +{ + return d->state; +} + +/*! + Returns the last error that occurred. This is useful to find out + what happened when receiving a requestFinished() or a done() + signal with the \c error argument \c true. + + If you start a new request, the error status is reset to \c NoError. +*/ +Q3Http::Error Q3Http::error() const +{ + return d->error; +} + +/*! + Returns a human-readable description of the last error that + occurred. This is useful to present a error message to the user + when receiving a requestFinished() or a done() signal with the \c + error argument \c true. +*/ +QString Q3Http::errorString() const +{ + return d->errorString; +} + +/*! \reimp +*/ +void Q3Http::timerEvent( QTimerEvent *e ) +{ + if ( e->timerId() == d->idleTimer ) { + killTimer( d->idleTimer ); + d->idleTimer = 0; + + if ( d->state == Connected ) { + finishedWithSuccess(); + } else if ( d->state != Unconnected ) { + setState( Unconnected ); + finishedWithSuccess(); + } + } else { + QObject::timerEvent( e ); + } +} + +void Q3Http::killIdleTimer() +{ + if (d->idleTimer) + killTimer( d->idleTimer ); + d->idleTimer = 0; +} + +void Q3Http::setState( int s ) +{ +#if defined(Q3HTTP_DEBUG) + qDebug( "Q3Http state changed %d -> %d", d->state, s ); +#endif + d->state = (State)s; + emit stateChanged( s ); +} + +void Q3Http::close() +{ + // If no connection is open -> ignore + if ( d->state == Closing || d->state == Unconnected ) + return; + + d->postDevice = 0; + setState( Closing ); + + // Already closed ? + if ( !d->socket.isOpen() ) { + d->idleTimer = startTimer( 0 ); + } else { + // Close now. + d->socket.close(); + + // Did close succeed immediately ? + if ( d->socket.state() == Q3Socket::Idle ) { + // Prepare to emit the requestFinished() signal. + d->idleTimer = startTimer( 0 ); + } + } +} + +/********************************************************************** + * + * Q3Http implementation of the Q3NetworkProtocol interface + * + *********************************************************************/ +/*! \reimp +*/ +int Q3Http::supportedOperations() const +{ + return OpGet | OpPut; +} + +/*! \reimp +*/ +void Q3Http::operationGet( Q3NetworkOperation *op ) +{ + connect( this, SIGNAL(readyRead(Q3HttpResponseHeader)), + this, SLOT(clientReply(Q3HttpResponseHeader)) ); + connect( this, SIGNAL(done(bool)), + this, SLOT(clientDone(bool)) ); + connect( this, SIGNAL(stateChanged(int)), + this, SLOT(clientStateChanged(int)) ); + + bytesRead = 0; + op->setState( StInProgress ); + Q3Url u( operationInProgress()->arg( 0 ) ); + Q3HttpRequestHeader header( QLatin1String("GET"), u.encodedPathAndQuery(), 1, 0 ); + header.setValue( QLatin1String("Host"), u.host() ); + setHost( u.host(), u.port() != -1 ? u.port() : 80 ); + request( header ); +} + +/*! \reimp +*/ +void Q3Http::operationPut( Q3NetworkOperation *op ) +{ + connect( this, SIGNAL(readyRead(Q3HttpResponseHeader)), + this, SLOT(clientReply(Q3HttpResponseHeader)) ); + connect( this, SIGNAL(done(bool)), + this, SLOT(clientDone(bool)) ); + connect( this, SIGNAL(stateChanged(int)), + this, SLOT(clientStateChanged(int)) ); + + bytesRead = 0; + op->setState( StInProgress ); + Q3Url u( operationInProgress()->arg( 0 ) ); + Q3HttpRequestHeader header( QLatin1String("POST"), u.encodedPathAndQuery(), 1, 0 ); + header.setValue( QLatin1String("Host"), u.host() ); + setHost( u.host(), u.port() != -1 ? u.port() : 80 ); + request( header, op->rawArg(1) ); +} + +void Q3Http::clientReply( const Q3HttpResponseHeader &rep ) +{ + Q3NetworkOperation *op = operationInProgress(); + if ( op ) { + if ( rep.statusCode() >= 400 && rep.statusCode() < 600 ) { + op->setState( StFailed ); + op->setProtocolDetail( + QString(QLatin1String("%1 %2")).arg(rep.statusCode()).arg(rep.reasonPhrase()) + ); + switch ( rep.statusCode() ) { + case 401: + case 403: + case 405: + op->setErrorCode( ErrPermissionDenied ); + break; + case 404: + op->setErrorCode(ErrFileNotExisting ); + break; + default: + if ( op->operation() == OpGet ) + op->setErrorCode( ErrGet ); + else + op->setErrorCode( ErrPut ); + break; + } + } + // ### In cases of an error, should we still emit the data() signals? + if ( op->operation() == OpGet && bytesAvailable() > 0 ) { + QByteArray ba = readAll(); + emit data( ba, op ); + bytesRead += ba.size(); + if ( rep.hasContentLength() ) { + emit dataTransferProgress( bytesRead, rep.contentLength(), op ); + } + } + } +} + +void Q3Http::clientDone( bool err ) +{ + disconnect( this, SIGNAL(readyRead(Q3HttpResponseHeader)), + this, SLOT(clientReply(Q3HttpResponseHeader)) ); + disconnect( this, SIGNAL(done(bool)), + this, SLOT(clientDone(bool)) ); + disconnect( this, SIGNAL(stateChanged(int)), + this, SLOT(clientStateChanged(int)) ); + + if ( err ) { + Q3NetworkOperation *op = operationInProgress(); + if ( op ) { + op->setState( Q3NetworkProtocol::StFailed ); + op->setProtocolDetail( errorString() ); + switch ( error() ) { + case ConnectionRefused: + op->setErrorCode( ErrHostNotFound ); + break; + case HostNotFound: + op->setErrorCode( ErrHostNotFound ); + break; + default: + if ( op->operation() == OpGet ) + op->setErrorCode( ErrGet ); + else + op->setErrorCode( ErrPut ); + break; + } + emit finished( op ); + } + } else { + Q3NetworkOperation *op = operationInProgress(); + if ( op ) { + if ( op->state() != StFailed ) { + op->setState( Q3NetworkProtocol::StDone ); + op->setErrorCode( Q3NetworkProtocol::NoError ); + } + emit finished( op ); + } + } + +} + +void Q3Http::clientStateChanged( int state ) +{ + if ( url() ) { + switch ( (State)state ) { + case Connecting: + emit connectionStateChanged( ConHostFound, QHttp::tr( "Host %1 found" ).arg( url()->host() ) ); + break; + case Sending: + emit connectionStateChanged( ConConnected, QHttp::tr( "Connected to host %1" ).arg( url()->host() ) ); + break; + case Unconnected: + emit connectionStateChanged( ConClosed, QHttp::tr( "Connection to %1 closed" ).arg( url()->host() ) ); + break; + default: + break; + } + } else { + switch ( (State)state ) { + case Connecting: + emit connectionStateChanged( ConHostFound, QHttp::tr( "Host found" ) ); + break; + case Sending: + emit connectionStateChanged( ConConnected, QHttp::tr( "Connected to host" ) ); + break; + case Unconnected: + emit connectionStateChanged( ConClosed, QHttp::tr( "Connection closed" ) ); + break; + default: + break; + } + } +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qt3support/network/q3http.h b/src/qt3support/network/q3http.h new file mode 100644 index 0000000..95be15d --- /dev/null +++ b/src/qt3support/network/q3http.h @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3HTTP_H +#define Q3HTTP_H + +#include <QtCore/qobject.h> +#include <Qt3Support/q3networkprotocol.h> +#include <QtCore/qmap.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +#ifndef QT_NO_NETWORKPROTOCOL_HTTP + +class Q3Socket; +class QTimerEvent; +class QTextStream; +class QIODevice; + +class Q3HttpPrivate; +class Q3HttpRequest; + +class Q_COMPAT_EXPORT Q3HttpHeader +{ +public: + Q3HttpHeader(); + Q3HttpHeader( const Q3HttpHeader& header ); + Q3HttpHeader( const QString& str ); + virtual ~Q3HttpHeader(); + + Q3HttpHeader& operator=( const Q3HttpHeader& h ); + + QString value( const QString& key ) const; + void setValue( const QString& key, const QString& value ); + void removeValue( const QString& key ); + + QStringList keys() const; + bool hasKey( const QString& key ) const; + + bool hasContentLength() const; + uint contentLength() const; + void setContentLength( int len ); + + bool hasContentType() const; + QString contentType() const; + void setContentType( const QString& type ); + + virtual QString toString() const; + bool isValid() const; + + virtual int majorVersion() const = 0; + virtual int minorVersion() const = 0; + +protected: + virtual bool parseLine( const QString& line, int number ); + bool parse( const QString& str ); + void setValid( bool ); + +private: + QMap<QString,QString> values; + bool valid; +}; + +class Q_COMPAT_EXPORT Q3HttpResponseHeader : public Q3HttpHeader +{ +private: + Q3HttpResponseHeader( int code, const QString& text = QString(), int majorVer = 1, int minorVer = 1 ); + Q3HttpResponseHeader( const QString& str ); + + void setStatusLine( int code, const QString& text = QString(), int majorVer = 1, int minorVer = 1 ); + +public: + Q3HttpResponseHeader(); + Q3HttpResponseHeader( const Q3HttpResponseHeader& header ); + + int statusCode() const; + QString reasonPhrase() const; + + int majorVersion() const; + int minorVersion() const; + + QString toString() const; + +protected: + bool parseLine( const QString& line, int number ); + +private: + int statCode; + QString reasonPhr; + int majVer; + int minVer; + + friend class Q3Http; +}; + +class Q_COMPAT_EXPORT Q3HttpRequestHeader : public Q3HttpHeader +{ +public: + Q3HttpRequestHeader(); + Q3HttpRequestHeader( const QString& method, const QString& path, int majorVer = 1, int minorVer = 1 ); + Q3HttpRequestHeader( const Q3HttpRequestHeader& header ); + Q3HttpRequestHeader( const QString& str ); + + void setRequest( const QString& method, const QString& path, int majorVer = 1, int minorVer = 1 ); + + QString method() const; + QString path() const; + + int majorVersion() const; + int minorVersion() const; + + QString toString() const; + +protected: + bool parseLine( const QString& line, int number ); + +private: + QString m; + QString p; + int majVer; + int minVer; +}; + +class Q_COMPAT_EXPORT Q3Http : public Q3NetworkProtocol +{ + Q_OBJECT + +public: + Q3Http(); + Q3Http( QObject* parent, const char* name = 0 ); // ### Qt 4.0: make parent=0 and get rid of the Q3Http() constructor + Q3Http( const QString &hostname, Q_UINT16 port=80, QObject* parent=0, const char* name = 0 ); + virtual ~Q3Http(); + + int supportedOperations() const; + + enum State { Unconnected, HostLookup, Connecting, Sending, Reading, Connected, Closing }; + enum Error { + NoError, + UnknownError, + HostNotFound, + ConnectionRefused, + UnexpectedClose, + InvalidResponseHeader, + WrongContentLength, + Aborted + }; + + int setHost(const QString &hostname, Q_UINT16 port=80 ); + + int get( const QString& path, QIODevice* to=0 ); + int post( const QString& path, QIODevice* data, QIODevice* to=0 ); + int post( const QString& path, const QByteArray& data, QIODevice* to=0 ); + int head( const QString& path ); + int request( const Q3HttpRequestHeader &header, QIODevice *device=0, QIODevice *to=0 ); + int request( const Q3HttpRequestHeader &header, const QByteArray &data, QIODevice *to=0 ); + + int closeConnection(); + + Q_ULONG bytesAvailable() const; + Q_LONG readBlock( char *data, Q_ULONG maxlen ); + QByteArray readAll(); + + int currentId() const; + QIODevice* currentSourceDevice() const; + QIODevice* currentDestinationDevice() const; + Q3HttpRequestHeader currentRequest() const; + bool hasPendingRequests() const; + void clearPendingRequests(); + + State state() const; + + Error error() const; + QString errorString() const; + +public Q_SLOTS: + void abort(); + +Q_SIGNALS: + void stateChanged( int ); + void responseHeaderReceived( const Q3HttpResponseHeader& resp ); + void readyRead( const Q3HttpResponseHeader& resp ); + void dataSendProgress( int, int ); + void dataReadProgress( int, int ); + + void requestStarted( int ); + void requestFinished( int, bool ); + void done( bool ); + +protected: + void operationGet( Q3NetworkOperation *op ); + void operationPut( Q3NetworkOperation *op ); + + void timerEvent( QTimerEvent * ); + +private Q_SLOTS: + void clientReply( const Q3HttpResponseHeader &rep ); + void clientDone( bool ); + void clientStateChanged( int ); + + void startNextRequest(); + void slotReadyRead(); + void slotConnected(); + void slotError( int ); + void slotClosed(); + void slotBytesWritten( int ); + +private: + Q3HttpPrivate *d; + void *unused; // ### Qt 4.0: remove this (in for binary compatibility) + int bytesRead; + + int addRequest( Q3HttpRequest * ); + void sendRequest(); + void finishedWithSuccess(); + void finishedWithError( const QString& detail, int errorCode ); + + void killIdleTimer(); + + void init(); + void setState( int ); + void close(); + + friend class Q3HttpNormalRequest; + friend class Q3HttpSetHostRequest; + friend class Q3HttpCloseRequest; + friend class Q3HttpPGHRequest; +}; + +#endif // QT_NO_NETWORKPROTOCOL_HTTP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3HTTP_H diff --git a/src/qt3support/network/q3localfs.cpp b/src/qt3support/network/q3localfs.cpp new file mode 100644 index 0000000..619d4c2 --- /dev/null +++ b/src/qt3support/network/q3localfs.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3localfs.h" + +#ifndef QT_NO_NETWORKPROTOCOL + +#include "qfileinfo.h" +#include "qfile.h" +#include "q3url.h" +#include "qurlinfo.h" +#include "qapplication.h" +#include "q3urloperator.h" +#include "qpointer.h" +#include "q3valuelist.h" + +QT_BEGIN_NAMESPACE + +//#define QLOCALFS_DEBUG + + +/*! + \class Q3LocalFs + \brief The Q3LocalFs class is an implementation of a + QNetworkProtocol that works on the local file system. + + \compat + + This class is derived from QNetworkProtocol. Q3LocalFs is not + normally used directly, but rather through a QUrlOperator, for + example: + \snippet doc/src/snippets/code/src_qt3support_network_q3localfs.cpp 0 + + This code will only work if the Q3LocalFs class is registered; to + register the class, you must call qInitNetworkProtocols() before + using a QUrlOperator with Q3LocalFs. + + If you really need to use Q3LocalFs directly, don't forget + to set its QUrlOperator with setUrl(). + + \sa Q3NetworkProtocol, Q3UrlOperator +*/ + +/*! + Constructor. +*/ + +Q3LocalFs::Q3LocalFs() + : Q3NetworkProtocol() +{ +} + +static int convertPermissions(QFileInfo *fi) +{ + int p = 0; + if ( fi->permission( QFileInfo::ReadOwner ) ) + p |= QUrlInfo::ReadOwner; + if ( fi->permission( QFileInfo::WriteOwner ) ) + p |= QUrlInfo::WriteOwner; + if ( fi->permission( QFileInfo::ExeOwner ) ) + p |= QUrlInfo::ExeOwner; + if ( fi->permission( QFileInfo::ReadGroup ) ) + p |= QUrlInfo::ReadGroup; + if ( fi->permission( QFileInfo::WriteGroup ) ) + p |= QUrlInfo::WriteGroup; + if ( fi->permission( QFileInfo::ExeGroup ) ) + p |= QUrlInfo::ExeGroup; + if ( fi->permission( QFileInfo::ReadOther ) ) + p |= QUrlInfo::ReadOther; + if ( fi->permission( QFileInfo::WriteOther ) ) + p |= QUrlInfo::WriteOther; + if ( fi->permission( QFileInfo::ExeOther ) ) + p |= QUrlInfo::ExeOther; + return p; +} + +/*! + \reimp +*/ + +void Q3LocalFs::operationListChildren( Q3NetworkOperation *op ) +{ +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: operationListChildren" ); +#endif + op->setState( StInProgress ); + + dir = QDir( url()->path() ); + dir.setNameFilter( url()->nameFilter() ); + dir.setMatchAllDirs( true ); + if ( !dir.isReadable() ) { + QString msg = tr( "Could not read directory\n%1" ).arg( url()->path() ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrListChildren ); + emit finished( op ); + return; + } + + QFileInfoList filist = dir.entryInfoList(QDir::All | QDir::Hidden | QDir::System); + if ( filist.isEmpty() ) { + QString msg = tr( "Could not read directory\n%1" ).arg( url()->path() ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrListChildren ); + emit finished( op ); + return; + } + + emit start( op ); + + Q3ValueList<QUrlInfo> infos; + for (int i = 0; i < filist.size(); ++i) { + QFileInfo fi = filist.at(i); + infos << QUrlInfo( fi.fileName(), convertPermissions(&fi), fi.owner(), fi.group(), + fi.size(), fi.lastModified(), fi.lastRead(), fi.isDir(), fi.isFile(), + fi.isSymLink(), fi.isWritable(), fi.isReadable(), fi.isExecutable() ); + } + emit newChildren( infos, op ); + op->setState( StDone ); + emit finished( op ); +} + +/*! + \reimp +*/ + +void Q3LocalFs::operationMkDir( Q3NetworkOperation *op ) +{ +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: operationMkDir" ); +#endif + op->setState( StInProgress ); + QString dirname = op->arg( 0 ); + + dir = QDir( url()->path() ); + if ( dir.mkdir( dirname ) ) { + QFileInfo fi( dir, dirname ); + QUrlInfo inf( fi.fileName(), convertPermissions(&fi), fi.owner(), fi.group(), + fi.size(), fi.lastModified(), fi.lastRead(), fi.isDir(), fi.isFile(), + fi.isSymLink(), fi.isWritable(), fi.isReadable(), fi.isExecutable() ); + emit newChild( inf, op ); + op->setState( StDone ); + emit createdDirectory( inf, op ); + emit finished( op ); + } else { + QString msg = tr( "Could not create directory\n%1" ).arg( dirname ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrMkDir ); + emit finished( op ); + } +} + +/*! + \reimp +*/ + +void Q3LocalFs::operationRemove( Q3NetworkOperation *op ) +{ +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: operationRemove" ); +#endif + op->setState( StInProgress ); + QString name = Q3Url( op->arg( 0 ) ).path(); + bool deleted = false; + + dir = QDir( url()->path() ); + + QFileInfo fi( dir, name ); + if ( fi.isDir() ) { + if ( dir.rmdir( name ) ) + deleted = true; + } + + if ( deleted || dir.remove( name ) ) { + op->setState( StDone ); + emit removed( op ); + emit finished( op ); + } else { + QString msg = tr( "Could not remove file or directory\n%1" ).arg( name ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrRemove ); + emit finished( op ); + } +} + +/*! + \reimp +*/ + +void Q3LocalFs::operationRename( Q3NetworkOperation *op ) +{ +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: operationRename" ); +#endif + op->setState( StInProgress ); + QString oldname = op->arg( 0 ); + QString newname = op->arg( 1 ); + + dir = QDir( url()->path() ); + if ( dir.rename( oldname, newname ) ) { + op->setState( StDone ); + emit itemChanged( op ); + emit finished( op ); + } else { + QString msg = tr( "Could not rename\n%1\nto\n%2" ).arg( oldname ).arg( newname ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrRename ); + emit finished( op ); + } +} + +/*! + \reimp +*/ + +void Q3LocalFs::operationGet( Q3NetworkOperation *op ) +{ +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: operationGet" ); +#endif + op->setState( StInProgress ); + QString from = Q3Url( op->arg( 0 ) ).path(); + + QFile f( from ); + if ( !f.open( IO_ReadOnly ) ) { +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: could not open %s", from.latin1() ); +#endif + QString msg = tr( "Could not open\n%1" ).arg( from ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrGet ); + emit finished( op ); + return; + } + + QByteArray s; + emit dataTransferProgress( 0, f.size(), op ); + if ( f.size() != 0 ) { + int blockSize = calcBlockSize( f.size() ); + if ( (int)f.size() < blockSize ) { + s.resize( f.size() ); + f.readBlock( s.data(), f.size() ); + emit data( s, op ); + emit dataTransferProgress( f.size(), f.size(), op ); +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: got all %d bytes at once", f.size() ); +#endif + } else { + s.resize( blockSize ); + int remaining = f.size(); + QPointer<QObject> that = this; + while ( that && remaining > 0 ) { + if ( operationInProgress() != op ) + return; + if ( remaining >= blockSize ) { + f.readBlock( s.data(), blockSize ); + emit data( s, op ); + emit dataTransferProgress( f.size() - remaining, f.size(), op ); + remaining -= blockSize; + } else { + s.resize( remaining ); + f.readBlock( s.data(), remaining ); + emit data( s, op ); + emit dataTransferProgress( f.size() - remaining, f.size(), op ); + remaining -= remaining; + } + qApp->processEvents(); + } + if ( !that ) + return; +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: got all %d bytes step by step", f.size() ); +#endif + emit dataTransferProgress( f.size(), f.size(), op ); + } + } + op->setState( StDone ); + f.close(); + emit finished( op ); +} + +/*! + \reimp +*/ + +void Q3LocalFs::operationPut( Q3NetworkOperation *op ) +{ +#ifdef QLOCALFS_DEBUG + qDebug( "Q3LocalFs: operationPut" ); +#endif + op->setState( StInProgress ); + QString to = Q3Url( op->arg( 0 ) ).path(); + + QFile f( to ); + if ( !f.open( IO_WriteOnly ) ) { + QString msg = tr( "Could not write\n%1" ).arg( to ); + op->setState( StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)ErrPut ); + emit finished( op ); + return; + } + + QByteArray ba( op->rawArg( 1 ) ); + emit dataTransferProgress( 0, ba.size(), op ); + int blockSize = calcBlockSize( ba.size() ); + if ( (int)ba.size() < blockSize ) { + f.writeBlock( ba.data(), ba.size() ); + emit dataTransferProgress( ba.size(), ba.size(), op ); + } else { + int i = 0; + while ( i + blockSize < (int)ba.size() - 1 ) { + if ( operationInProgress() != op ) + return; + f.writeBlock( &ba.data()[ i ], blockSize ); + f.flush(); + emit dataTransferProgress( i + blockSize, ba.size(), op ); + i += blockSize; + QPointer<QObject> that = this; + qApp->processEvents(); + if (!that) + return; + } + if ( i < (int)ba.size() - 1 ) + f.writeBlock( &ba.data()[ i ], ba.size() - i ); + emit dataTransferProgress( ba.size(), ba.size(), op ); + } + op->setState( StDone ); + f.close(); + emit finished( op ); +} + +/*! + \reimp +*/ + +int Q3LocalFs::supportedOperations() const +{ + return OpListChildren | OpMkDir | OpRemove | OpRename | OpGet | OpPut; +} + +/*! + \internal +*/ + +int Q3LocalFs::calcBlockSize( int totalSize ) const +{ + if ( totalSize == 0 ) + return 1024; + int s = totalSize / 100; + // we want a block size between 1KB and 1MB + if ( s < 1024 ) + s = 1024; + if ( s > 1048576 ) + s = 1048576; + return s; +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKPROTOCOL diff --git a/src/qt3support/network/q3localfs.h b/src/qt3support/network/q3localfs.h new file mode 100644 index 0000000..9ce4f8a --- /dev/null +++ b/src/qt3support/network/q3localfs.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3LOCALFS_H +#define Q3LOCALFS_H + +#include <Qt3Support/q3networkprotocol.h> +#include <QtCore/qdir.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_NETWORKPROTOCOL + +class Q_COMPAT_EXPORT Q3LocalFs : public Q3NetworkProtocol +{ + Q_OBJECT + +public: + Q3LocalFs(); + virtual int supportedOperations() const; + +protected: + virtual void operationListChildren( Q3NetworkOperation *op ); + virtual void operationMkDir( Q3NetworkOperation *op ); + virtual void operationRemove( Q3NetworkOperation *op ); + virtual void operationRename( Q3NetworkOperation *op ); + virtual void operationGet( Q3NetworkOperation *op ); + virtual void operationPut( Q3NetworkOperation *op ); + +private: + int calcBlockSize( int totalSize ) const; + QDir dir; + +}; + +#endif // QT_NO_NETWORKPROTOCOL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3LOCALFS_H diff --git a/src/qt3support/network/q3network.cpp b/src/qt3support/network/q3network.cpp new file mode 100644 index 0000000..e3cbeb9 --- /dev/null +++ b/src/qt3support/network/q3network.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3network.h" + +#ifndef QT_NO_NETWORK + +#include "q3networkprotocol.h" + +// protocols +#include "q3ftp.h" +#include "q3http.h" + +QT_BEGIN_NAMESPACE + +/*! + \relates Q3UrlOperator + + This function registers the network protocols for FTP and HTTP. + You must call this function before you use QUrlOperator for + these protocols. +*/ +void q3InitNetworkProtocols() +{ +#ifndef QT_NO_NETWORKPROTOCOL_FTP + Q3NetworkProtocol::registerNetworkProtocol( QLatin1String("ftp"), new Q3NetworkProtocolFactory< Q3Ftp > ); +#endif +#ifndef QT_NO_NETWORKPROTOCOL_HTTP + Q3NetworkProtocol::registerNetworkProtocol( QLatin1String("http"), new Q3NetworkProtocolFactory< Q3Http > ); +#endif +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORK diff --git a/src/qt3support/network/q3network.h b/src/qt3support/network/q3network.h new file mode 100644 index 0000000..b363b74 --- /dev/null +++ b/src/qt3support/network/q3network.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3NETWORK_H +#define Q3NETWORK_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +#ifndef QT_NO_NETWORK + +Q_COMPAT_EXPORT void q3InitNetworkProtocols(); + +#endif // QT_NO_NETWORK + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3NETWORK_H diff --git a/src/qt3support/network/q3networkprotocol.cpp b/src/qt3support/network/q3networkprotocol.cpp new file mode 100644 index 0000000..d02f4ea --- /dev/null +++ b/src/qt3support/network/q3networkprotocol.cpp @@ -0,0 +1,1209 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3networkprotocol.h" + +#ifndef QT_NO_NETWORKPROTOCOL + +#include "q3localfs.h" +#include "q3urloperator.h" +#include "qtimer.h" +#include "qmap.h" +#include "q3ptrqueue.h" +#include "q3valuelist.h" +#include "qurlinfo.h" +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +//#define Q3NETWORKPROTOCOL_DEBUG +#define NETWORK_OP_DELAY 1000 + +extern Q_COMPAT_EXPORT Q3NetworkProtocolDict *q3networkProtocolRegister; + +Q3NetworkProtocolDict *q3networkProtocolRegister = 0; + +class Q3NetworkProtocolPrivate +{ +public: + Q3NetworkProtocolPrivate( Q3NetworkProtocol *p ) + { + url = 0; + opInProgress = 0; + opStartTimer = new QTimer( p ); + removeTimer = new QTimer( p ); + operationQueue.setAutoDelete( false ); + autoDelete = false; + removeInterval = 10000; + oldOps.setAutoDelete( false ); + } + + ~Q3NetworkProtocolPrivate() + { + removeTimer->stop(); + if ( opInProgress ) { + if ( opInProgress == operationQueue.head() ) + operationQueue.dequeue(); + opInProgress->free(); + } + while ( operationQueue.head() ) { + operationQueue.head()->free(); + operationQueue.dequeue(); + } + while ( oldOps.first() ) { + oldOps.first()->free(); + oldOps.removeFirst(); + } + delete opStartTimer; + } + + Q3UrlOperator *url; + Q3PtrQueue< Q3NetworkOperation > operationQueue; + Q3NetworkOperation *opInProgress; + QTimer *opStartTimer, *removeTimer; + int removeInterval; + bool autoDelete; + Q3PtrList< Q3NetworkOperation > oldOps; +}; + +/*! + \class Q3NetworkProtocol + \brief The Q3NetworkProtocol class provides a common API for network protocols. + + \compat + + This is a base class which should be used for network protocols + implementations that can then be used in Qt (e.g. in the file + dialog) together with the Q3UrlOperator. + + The easiest way to implement a new network protocol is to + reimplement the operation*() methods, e.g. operationGet(), etc. + Only the supported operations should be reimplemented. To specify + which operations are supported, also reimplement + supportedOperations() and return an int that is OR'd together + using the supported operations from the \l + Q3NetworkProtocol::Operation enum. + + When you implement a network protocol this way, it is important to + emit the correct signals. Also, always emit the finished() signal + when an operation is done (on success \e and on failure). Qt + relies on correctly emitted finished() signals. +*/ + +/*! + \fn void Q3NetworkProtocol::newChildren( const Q3ValueList<QUrlInfo> &i, Q3NetworkOperation *op ) + + This signal is emitted after listChildren() was called and new + children (files) have been read from the list of files. \a i holds + the information about the new children. \a op is the pointer to + the operation object which contains all the information about the + operation, including the state, etc. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. + + When implementing your own network protocol and reading children, + you usually don't read one child at once, but rather a list of + them. That's why this signal takes a list of QUrlInfo objects. If + you prefer to read just one child at a time you can use the + convenience signal newChild(), which takes a single QUrlInfo + object. +*/ + +/*! + \fn void Q3NetworkProtocol::newChild( const QUrlInfo &i, Q3NetworkOperation *op ) + + This signal is emitted if a new child (file) has been read. + Q3NetworkProtocol automatically connects it to a slot which creates + a list of QUrlInfo objects (with just one QUrlInfo \a i) and emits + the newChildren() signal with this list. \a op is the pointer to + the operation object which contains all the information about the + operation that has finished, including the state, etc. + + This is just a convenience signal useful for implementing your own + network protocol. In all other cases connect to the newChildren() + signal with its list of QUrlInfo objects. +*/ + +/*! + \fn void Q3NetworkProtocol::finished( Q3NetworkOperation *op ) + + This signal is emitted when an operation finishes. This signal is + always emitted, for both success and failure. \a op is the pointer + to the operation object which contains all the information about + the operation, including the state, etc. Check the state and error + code of the operation object to determine whether or not the + operation was successful. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::start( Q3NetworkOperation *op ) + + Some operations (such as listChildren()) emit this signal when + they start processing the operation. \a op is the pointer to the + operation object which contains all the information about the + operation, including the state, etc. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::createdDirectory( const QUrlInfo &i, Q3NetworkOperation *op ) + + This signal is emitted when mkdir() has been successful and the + directory has been created. \a i holds the information about the + new directory. \a op is the pointer to the operation object which + contains all the information about the operation, including the + state, etc. Using op->arg( 0 ), you can get the file name of the + new directory. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::removed( Q3NetworkOperation *op ) + + This signal is emitted when remove() has been succesiisful and the + file has been removed. \a op holds the file name of the removed + file in the first argument, accessible with op->arg( 0 ). \a op is + the pointer to the operation object which contains all the + information about the operation, including the state, etc. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::itemChanged( Q3NetworkOperation *op ) + + This signal is emitted whenever a file which is a child of this + URL has been changed, e.g. by successfully calling rename(). \a op + holds the original and the new file names in the first and second + arguments, accessible with op->arg( 0 ) and op->arg( 1 ) + respectively. \a op is the pointer to the operation object which + contains all the information about the operation, including the + state, etc. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::data( const QByteArray &data, + Q3NetworkOperation *op ) + + This signal is emitted when new \a data has been received after + calling get() or put(). \a op holds the name of the file from + which data is retrieved or uploaded in its first argument, and the + (raw) data in its second argument. You can get them with + op->arg( 0 ) and op->rawArg( 1 ). \a op is the pointer to the + operation object, which contains all the information about the + operation, including the state, etc. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator (which is used by the network + protocol) emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::dataTransferProgress( int bytesDone, int bytesTotal, Q3NetworkOperation *op ) + + This signal is emitted during the transfer of data (using put() or + get()). \a bytesDone is how many bytes of \a bytesTotal have been + transferred. \a bytesTotal may be -1, which means that the total + number of bytes is not known. \a op is the pointer to the + operation object which contains all the information about the + operation, including the state, etc. + + When a protocol emits this signal, Q3NetworkProtocol is smart + enough to let the Q3UrlOperator, which is used by the network + protocol, emit its corresponding signal. +*/ + +/*! + \fn void Q3NetworkProtocol::connectionStateChanged( int state, const QString &data ) + + This signal is emitted whenever the state of the connection of the + network protocol is changed. \a state describes the new state, + which is one of, \c ConHostFound, \c ConConnected or \c ConClosed. + \a data is a message text. +*/ + +/*! + \enum Q3NetworkProtocol::State + + This enum contains the state that a Q3NetworkOperation can have. + + \value StWaiting The operation is in the Q3NetworkProtocol's queue + waiting to be prcessed. + + \value StInProgress The operation is being processed. + + \value StDone The operation has been processed successfully. + + \value StFailed The operation has been processed but an error occurred. + + \value StStopped The operation has been processed but has been + stopped before it finished, and is waiting to be processed. + +*/ + +/*! + \enum Q3NetworkProtocol::Operation + + This enum lists the possible operations that a network protocol + can support. supportedOperations() returns an int of these that is + OR'd together. Also, the type() of a Q3NetworkOperation is always + one of these values. + + \value OpListChildren List the children of a URL, e.g. of a directory. + \value OpMkDir Create a directory. + \value OpRemove Remove a child (e.g. a file). + \value OpRename Rename a child (e.g. a file). + \value OpGet Get data from a location. + \value OpPut Put data to a location. + \omitvalue OpMkdir +*/ + +/*! + \enum Q3NetworkProtocol::ConnectionState + + When the connection state of a network protocol changes it emits + the signal connectionStateChanged(). The first argument is one of + the following values: + + \value ConHostFound Host has been found. + \value ConConnected Connection to the host has been established. + \value ConClosed Connection has been closed. +*/ + +/*! + \enum Q3NetworkProtocol::Error + + When an operation fails (finishes unsuccessfully), the + Q3NetworkOperation of the operation returns an error code which has + one of the following values: + + \value NoError No error occurred. + + \value ErrValid The URL you are operating on is not valid. + + \value ErrUnknownProtocol There is no protocol implementation + available for the protocol of the URL you are operating on (e.g. + if the protocol is http and no http implementation has been + registered). + + \value ErrUnsupported The operation is not supported by the + protocol. + + \value ErrParse The URL could not be parsed correctly. + + \value ErrLoginIncorrect You needed to login but the username + or password is wrong. + + \value ErrHostNotFound The specified host (in the URL) couldn't + be found. + + \value ErrListChildren An error occurred while listing the + children (files). + + \value ErrMkDir An error occurred when creating a directory. + + \value ErrRemove An error occurred when removing a child (file). + + \value ErrRename An error occurred when renaming a child (file). + + \value ErrGet An error occurred while getting (retrieving) data. + + \value ErrPut An error occurred while putting (uploading) data. + + \value ErrFileNotExisting A file which is needed by the operation + doesn't exist. + + \value ErrPermissionDenied Permission for doing the operation has + been denied. + \omitvalue ErrMkdir + \omitvalue ErrListChlidren + + You should also use these error codes when implementing custom + network protocols. If this is not possible, you can define your own + error codes by using integer values that don't conflict with any + of these values. +*/ + +/*! + Constructor of the network protocol base class. Does some + initialization and connecting of signals and slots. +*/ + +Q3NetworkProtocol::Q3NetworkProtocol() + : QObject() +{ + d = new Q3NetworkProtocolPrivate( this ); + + connect( d->opStartTimer, SIGNAL(timeout()), + this, SLOT(startOps()) ); + connect( d->removeTimer, SIGNAL(timeout()), + this, SLOT(removeMe()) ); + + if ( url() ) { + connect( this, SIGNAL(data(QByteArray,Q3NetworkOperation*)), + url(), SIGNAL(data(QByteArray,Q3NetworkOperation*)) ); + connect( this, SIGNAL(finished(Q3NetworkOperation*)), + url(), SIGNAL(finished(Q3NetworkOperation*)) ); + connect( this, SIGNAL(start(Q3NetworkOperation*)), + url(), SIGNAL(start(Q3NetworkOperation*)) ); + connect( this, SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)), + url(), SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)) ); + connect( this, SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)), + url(), SLOT(addEntry(Q3ValueList<QUrlInfo>)) ); + connect( this, SIGNAL(createdDirectory(QUrlInfo,Q3NetworkOperation*)), + url(), SIGNAL(createdDirectory(QUrlInfo,Q3NetworkOperation*)) ); + connect( this, SIGNAL(removed(Q3NetworkOperation*)), + url(), SIGNAL(removed(Q3NetworkOperation*)) ); + connect( this, SIGNAL(itemChanged(Q3NetworkOperation*)), + url(), SIGNAL(itemChanged(Q3NetworkOperation*)) ); + connect( this, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)), + url(), SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)) ); + connect( this, SIGNAL(connectionStateChanged(int,QString)), + url(), SIGNAL(connectionStateChanged(int,QString)) ); + } + + connect( this, SIGNAL(finished(Q3NetworkOperation*)), + this, SLOT(processNextOperation(Q3NetworkOperation*)) ); + connect( this, SIGNAL(newChild(QUrlInfo,Q3NetworkOperation*)), + this, SLOT(emitNewChildren(QUrlInfo,Q3NetworkOperation*)) ); + +} + +/*! + Destructor. +*/ + +Q3NetworkProtocol::~Q3NetworkProtocol() +{ + delete d; +} + +/*! + Sets the Q3UrlOperator, on which the protocol works, to \a u. + + \sa Q3UrlOperator +*/ + +void Q3NetworkProtocol::setUrl( Q3UrlOperator *u ) +{ + if ( url() ) { + disconnect( this, SIGNAL(data(QByteArray,Q3NetworkOperation*)), + url(), SIGNAL(data(QByteArray,Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(finished(Q3NetworkOperation*)), + url(), SIGNAL(finished(Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(start(Q3NetworkOperation*)), + url(), SIGNAL(start(Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)), + url(), SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)), + url(), SLOT(addEntry(Q3ValueList<QUrlInfo>)) ); + disconnect( this, SIGNAL(createdDirectory(QUrlInfo,Q3NetworkOperation*)), + url(), SIGNAL(createdDirectory(QUrlInfo,Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(removed(Q3NetworkOperation*)), + url(), SIGNAL(removed(Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(itemChanged(Q3NetworkOperation*)), + url(), SIGNAL(itemChanged(Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)), + url(), SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)) ); + disconnect( this, SIGNAL(connectionStateChanged(int,QString)), + url(), SIGNAL(connectionStateChanged(int,QString)) ); + } + + + // ### if autoDelete is true, we should delete the Q3UrlOperator (something + // like below; but that is not possible since it would delete this, too). + //if ( d->autoDelete && (d->url!=u) ) { + // delete d->url; // destructor deletes the network protocol + //} + d->url = u; + + if ( url() ) { + connect( this, SIGNAL(data(QByteArray,Q3NetworkOperation*)), + url(), SIGNAL(data(QByteArray,Q3NetworkOperation*)) ); + connect( this, SIGNAL(finished(Q3NetworkOperation*)), + url(), SIGNAL(finished(Q3NetworkOperation*)) ); + connect( this, SIGNAL(start(Q3NetworkOperation*)), + url(), SIGNAL(start(Q3NetworkOperation*)) ); + connect( this, SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)), + url(), SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)) ); + connect( this, SIGNAL(newChildren(Q3ValueList<QUrlInfo>,Q3NetworkOperation*)), + url(), SLOT(addEntry(Q3ValueList<QUrlInfo>)) ); + connect( this, SIGNAL(createdDirectory(QUrlInfo,Q3NetworkOperation*)), + url(), SIGNAL(createdDirectory(QUrlInfo,Q3NetworkOperation*)) ); + connect( this, SIGNAL(removed(Q3NetworkOperation*)), + url(), SIGNAL(removed(Q3NetworkOperation*)) ); + connect( this, SIGNAL(itemChanged(Q3NetworkOperation*)), + url(), SIGNAL(itemChanged(Q3NetworkOperation*)) ); + connect( this, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)), + url(), SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)) ); + connect( this, SIGNAL(connectionStateChanged(int,QString)), + url(), SIGNAL(connectionStateChanged(int,QString)) ); + } + + if ( !d->opInProgress && !d->operationQueue.isEmpty() ) + d->opStartTimer->start( 0, true ); +} + +/*! + For processing operations the network protocol base class calls + this method quite often. This should be reimplemented by new + network protocols. It should return true if the connection is OK + (open); otherwise it should return false. If the connection is not + open the protocol should open it. + + If the connection can't be opened (e.g. because you already tried + but the host couldn't be found), set the state of \a op to + Q3NetworkProtocol::StFailed and emit the finished() signal with + this Q3NetworkOperation as argument. + + \a op is the operation that needs an open connection. +*/ + +bool Q3NetworkProtocol::checkConnection( Q3NetworkOperation * ) +{ + return true; +} + +/*! + Returns an int that is OR'd together using the enum values of + \l{Q3NetworkProtocol::Operation}, which describes which operations + are supported by the network protocol. Should be reimplemented by + new network protocols. +*/ + +int Q3NetworkProtocol::supportedOperations() const +{ + return 0; +} + +/*! + Adds the operation \a op to the operation queue. The operation + will be processed as soon as possible. This method returns + immediately. +*/ + +void Q3NetworkProtocol::addOperation( Q3NetworkOperation *op ) +{ +#ifdef Q3NETWORKPROTOCOL_DEBUG + qDebug( "Q3NetworkOperation: addOperation: %p %d", op, op->operation() ); +#endif + d->operationQueue.enqueue( op ); + if ( !d->opInProgress ) + d->opStartTimer->start( 0, true ); +} + +/*! + Static method to register a network protocol for Qt. For example, + if you have an implementation of NNTP (called Nntp) which is + derived from Q3NetworkProtocol, call: + \snippet doc/src/snippets/code/src_qt3support_network_q3networkprotocol.cpp 0 + after which your implementation is registered for future nntp + operations. + + The name of the protocol is given in \a protocol and a pointer to + the protocol factory is given in \a protocolFactory. +*/ + +void Q3NetworkProtocol::registerNetworkProtocol( const QString &protocol, + Q3NetworkProtocolFactoryBase *protocolFactory ) +{ + if ( !q3networkProtocolRegister ) { + q3networkProtocolRegister = new Q3NetworkProtocolDict; + Q3NetworkProtocol::registerNetworkProtocol( QLatin1String("file"), new Q3NetworkProtocolFactory< Q3LocalFs > ); + } + + q3networkProtocolRegister->insert( protocol, protocolFactory ); +} + +/*! + Static method to get a new instance of the network protocol \a + protocol. For example, if you need to do some FTP operations, do + the following: + \snippet doc/src/snippets/code/src_qt3support_network_q3networkprotocol.cpp 1 + This returns a pointer to a new instance of an ftp implementation + or null if no protocol for ftp was registered. The ownership of + the pointer is transferred to you, so you must delete it if you + don't need it anymore. + + Normally you should not work directly with network protocols, so + you will not need to call this method yourself. Instead, use + Q3UrlOperator, which makes working with network protocols much more + convenient. + + \sa Q3UrlOperator +*/ + +Q3NetworkProtocol *Q3NetworkProtocol::getNetworkProtocol( const QString &protocol ) +{ + if ( !q3networkProtocolRegister ) { + q3networkProtocolRegister = new Q3NetworkProtocolDict; + Q3NetworkProtocol::registerNetworkProtocol( QLatin1String("file"), new Q3NetworkProtocolFactory< Q3LocalFs > ); + } + + if ( protocol.isNull() ) + return 0; + + Q3NetworkProtocolFactoryBase *factory = q3networkProtocolRegister->find( protocol ); + if ( factory ) + return factory->createObject(); + + return 0; +} + +/*! + Returns true if the only protocol registered is for working on the + local filesystem; returns false if other network protocols are + also registered. +*/ + +bool Q3NetworkProtocol::hasOnlyLocalFileSystem() +{ + if ( !q3networkProtocolRegister ) + return false; + + Q3DictIterator< Q3NetworkProtocolFactoryBase > it( *q3networkProtocolRegister ); + for ( ; it.current(); ++it ) + if ( it.currentKey() != QLatin1String("file") ) + return false; + return true; +} + +/*! + \internal + Starts processing network operations. +*/ + +void Q3NetworkProtocol::startOps() +{ +#ifdef Q3NETWORKPROTOCOL_DEBUG + qDebug( "Q3NetworkOperation: start processing operations" ); +#endif + processNextOperation( 0 ); +} + +/*! + \internal + Processes the operation \a op. It calls the + corresponding operation[something]( Q3NetworkOperation * ) + methods. +*/ + +void Q3NetworkProtocol::processOperation( Q3NetworkOperation *op ) +{ + if ( !op ) + return; + + switch ( op->operation() ) { + case OpListChildren: + operationListChildren( op ); + break; + case OpMkDir: + operationMkDir( op ); + break; + case OpRemove: + operationRemove( op ); + break; + case OpRename: + operationRename( op ); + break; + case OpGet: + operationGet( op ); + break; + case OpPut: + operationPut( op ); + break; + } +} + +/*! + When implementing a new network protocol, this method should be + reimplemented if the protocol supports listing children (files); + this method should then process this Q3NetworkOperation. + + \a op is the pointer to the operation object which contains all + the information on the operation that has finished, including the + state, etc. +*/ + +void Q3NetworkProtocol::operationListChildren( Q3NetworkOperation * ) +{ +} + +/*! + When implementing a new network protocol, this method should be + reimplemented if the protocol supports making directories; this + method should then process this Q3NetworkOperation. + + \a op is the pointer to the operation object which contains all + the information on the operation that has finished, including the + state, etc. +*/ + +void Q3NetworkProtocol::operationMkDir( Q3NetworkOperation * ) +{ +} + +/*! + When implementing a new network protocol, this method should be + reimplemented if the protocol supports removing children (files); + this method should then process this Q3NetworkOperation. + + \a op is the pointer to the operation object which contains all + the information on the operation that has finished, including the + state, etc. +*/ + +void Q3NetworkProtocol::operationRemove( Q3NetworkOperation * ) +{ +} + +/*! + When implementing a new network protocol, this method should be + reimplemented if the protocol supports renaming children (files); + this method should then process this Q3NetworkOperation. + + \a op is the pointer to the operation object which contains all + the information on the operation that has finished, including the + state, etc. +*/ + +void Q3NetworkProtocol::operationRename( Q3NetworkOperation * ) +{ +} + +/*! + When implementing a new network protocol, this method should be + reimplemented if the protocol supports getting data; this method + should then process the Q3NetworkOperation. + + \a op is the pointer to the operation object which contains all + the information on the operation that has finished, including the + state, etc. +*/ + +void Q3NetworkProtocol::operationGet( Q3NetworkOperation * ) +{ +} + +/*! + When implementing a new network protocol, this method should be + reimplemented if the protocol supports putting (uploading) data; + this method should then process the Q3NetworkOperation. + + \a op is the pointer to the operation object which contains all + the information on the operation that has finished, including the + state, etc. +*/ + +void Q3NetworkProtocol::operationPut( Q3NetworkOperation * ) +{ +} + +/*! \internal +*/ + +void Q3NetworkProtocol::operationPutChunk( Q3NetworkOperation * ) +{ +} + +/*! + \internal + Handles operations. Deletes the previous operation object and + tries to process the next operation. It also checks the connection state + and only processes the next operation, if the connection of the protocol + is open. Otherwise it waits until the protocol opens the connection. +*/ + +void Q3NetworkProtocol::processNextOperation( Q3NetworkOperation *old ) +{ +#ifdef Q3NETWORKPROTOCOL_DEBUG + qDebug( "Q3NetworkOperation: process next operation, old: %p", old ); +#endif + d->removeTimer->stop(); + + if ( old ) + d->oldOps.append( old ); + if ( d->opInProgress && d->opInProgress!=old ) + d->oldOps.append( d->opInProgress ); + + if ( d->operationQueue.isEmpty() ) { + d->opInProgress = 0; + if ( d->autoDelete ) + d->removeTimer->start( d->removeInterval, true ); + return; + } + + Q3NetworkOperation *op = d->operationQueue.head(); + + d->opInProgress = op; + + if ( !checkConnection( op ) ) { + if ( op->state() != Q3NetworkProtocol::StFailed ) { + d->opStartTimer->start( 0, true ); + } else { + d->operationQueue.dequeue(); + clearOperationQueue(); + emit finished( op ); + } + + return; + } + + d->opInProgress = op; + d->operationQueue.dequeue(); + processOperation( op ); +} + +/*! + Returns the Q3UrlOperator on which the protocol works. +*/ + +Q3UrlOperator *Q3NetworkProtocol::url() const +{ + return d->url; +} + +/*! + Returns the operation, which is being processed, or 0 of no + operation is being processed at the moment. +*/ + +Q3NetworkOperation *Q3NetworkProtocol::operationInProgress() const +{ + return d->opInProgress; +} + +/*! + Clears the operation queue. +*/ + +void Q3NetworkProtocol::clearOperationQueue() +{ + d->operationQueue.dequeue(); + d->operationQueue.setAutoDelete( true ); + d->operationQueue.clear(); +} + +/*! + Stops the current operation that is being processed and clears all + waiting operations. +*/ + +void Q3NetworkProtocol::stop() +{ + Q3NetworkOperation *op = d->opInProgress; + clearOperationQueue(); + if ( op ) { + op->setState( StStopped ); + op->setProtocolDetail( tr( "Operation stopped by the user" ) ); + emit finished( op ); + setUrl( 0 ); + op->free(); + } +} + +/*! + Because it's sometimes hard to take care of removing network + protocol instances, Q3NetworkProtocol provides an auto-delete + mechanism. If you set \a b to true, the network protocol instance + is removed after it has been inactive for \a i milliseconds (i.e. + \a i milliseconds after the last operation has been processed). + If you set \a b to false the auto-delete mechanism is switched + off. + + If you switch on auto-delete, the Q3NetworkProtocol also deletes + its Q3UrlOperator. +*/ + +void Q3NetworkProtocol::setAutoDelete( bool b, int i ) +{ + d->autoDelete = b; + d->removeInterval = i; +} + +/*! + Returns true if auto-deleting is enabled; otherwise returns false. + + \sa Q3NetworkProtocol::setAutoDelete() +*/ + +bool Q3NetworkProtocol::autoDelete() const +{ + return d->autoDelete; +} + +/*! + \internal +*/ + +void Q3NetworkProtocol::removeMe() +{ + if ( d->autoDelete ) { +#ifdef Q3NETWORKPROTOCOL_DEBUG + qDebug( "Q3NetworkOperation: autodelete of Q3NetworkProtocol %p", this ); +#endif + delete d->url; // destructor deletes the network protocol + } +} + +void Q3NetworkProtocol::emitNewChildren( const QUrlInfo &i, Q3NetworkOperation *op ) +{ + Q3ValueList<QUrlInfo> lst; + lst << i; + emit newChildren( lst, op ); +} + +class Q3NetworkOperationPrivate +{ +public: + Q3NetworkProtocol::Operation operation; + Q3NetworkProtocol::State state; + QMap<int, QString> args; + QMap<int, QByteArray> rawArgs; + QString protocolDetail; + int errorCode; + QTimer *deleteTimer; +}; + +/*! + \class Q3NetworkOperation + + \brief The Q3NetworkOperation class provides common operations for network protocols. + + \compat + + An object is created to describe the operation and the current + state for each operation that a network protocol should process. + + \sa Q3NetworkProtocol +*/ + +/*! + Constructs a network operation object. \a operation is the type of + the operation, and \a arg0, \a arg1 and \a arg2 are the first + three arguments of the operation. The state is initialized to + Q3NetworkProtocol::StWaiting. + + \sa Q3NetworkProtocol::Operation Q3NetworkProtocol::State +*/ + +Q3NetworkOperation::Q3NetworkOperation( Q3NetworkProtocol::Operation operation, + const QString &arg0, const QString &arg1, + const QString &arg2 ) +{ + d = new Q3NetworkOperationPrivate; + d->deleteTimer = new QTimer( this ); + connect( d->deleteTimer, SIGNAL(timeout()), + this, SLOT(deleteMe()) ); + d->operation = operation; + d->state = Q3NetworkProtocol::StWaiting; + d->args[ 0 ] = arg0; + d->args[ 1 ] = arg1; + d->args[ 2 ] = arg2; + d->rawArgs[ 0 ] = QByteArray( 0 ); + d->rawArgs[ 1 ] = QByteArray( 0 ); + d->rawArgs[ 2 ] = QByteArray( 0 ); + d->protocolDetail.clear(); + d->errorCode = (int)Q3NetworkProtocol::NoError; +} + +/*! + Constructs a network operation object. \a operation is the type of + the operation, and \a arg0, \a arg1 and \a arg2 are the first + three raw data arguments of the operation. The state is + initialized to Q3NetworkProtocol::StWaiting. + + \sa Q3NetworkProtocol::Operation Q3NetworkProtocol::State +*/ + +Q3NetworkOperation::Q3NetworkOperation( Q3NetworkProtocol::Operation operation, + const QByteArray &arg0, const QByteArray &arg1, + const QByteArray &arg2 ) +{ + d = new Q3NetworkOperationPrivate; + d->deleteTimer = new QTimer( this ); + connect( d->deleteTimer, SIGNAL(timeout()), + this, SLOT(deleteMe()) ); + d->operation = operation; + d->state = Q3NetworkProtocol::StWaiting; + d->args[ 0 ].clear(); + d->args[ 1 ].clear(); + d->args[ 2 ].clear(); + d->rawArgs[ 0 ] = arg0; + d->rawArgs[ 1 ] = arg1; + d->rawArgs[ 2 ] = arg2; + d->protocolDetail.clear(); + d->errorCode = (int)Q3NetworkProtocol::NoError; +} + +/*! + Destructor. +*/ + +Q3NetworkOperation::~Q3NetworkOperation() +{ + qDeleteInEventHandler(d->deleteTimer); + delete d; +} + +/*! + Sets the \a state of the operation object. This should be done by + the network protocol during processing; at the end it should be + set to Q3NetworkProtocol::StDone or Q3NetworkProtocol::StFailed, + depending on success or failure. + + \sa Q3NetworkProtocol::State +*/ + +void Q3NetworkOperation::setState( Q3NetworkProtocol::State state ) +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + d->state = state; +} + +/*! + If the operation failed, the error message can be specified as \a + detail. +*/ + +void Q3NetworkOperation::setProtocolDetail( const QString &detail ) +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + d->protocolDetail = detail; +} + +/*! + Sets the error code to \a ec. + + If the operation failed, the protocol should set an error code to + describe the error in more detail. If possible, one of the error + codes defined in Q3NetworkProtocol should be used. + + \sa setProtocolDetail() Q3NetworkProtocol::Error +*/ + +void Q3NetworkOperation::setErrorCode( int ec ) +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + d->errorCode = ec; +} + +/*! + Sets the network operation's \a{num}-th argument to \a arg. +*/ + +void Q3NetworkOperation::setArg( int num, const QString &arg ) +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + d->args[ num ] = arg; +} + +/*! + Sets the network operation's \a{num}-th raw data argument to \a arg. +*/ + +void Q3NetworkOperation::setRawArg( int num, const QByteArray &arg ) +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + d->rawArgs[ num ] = arg; +} + +/*! + Returns the type of the operation. +*/ + +Q3NetworkProtocol::Operation Q3NetworkOperation::operation() const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->operation; +} + +/*! + Returns the state of the operation. You can determine whether an + operation is still waiting to be processed, is being processed, + has been processed successfully, or failed. +*/ + +Q3NetworkProtocol::State Q3NetworkOperation::state() const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->state; +} + +/*! + Returns the operation's \a{num}-th argument. If this argument was + not already set, an empty string is returned. +*/ + +QString Q3NetworkOperation::arg( int num ) const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->args[ num ]; +} + +/*! + Returns the operation's \a{num}-th raw data argument. If this + argument was not already set, an empty bytearray is returned. +*/ + +QByteArray Q3NetworkOperation::rawArg( int num ) const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->rawArgs[ num ]; +} + +/*! + Returns a detailed error message for the last error. This must + have been set using setProtocolDetail(). +*/ + +QString Q3NetworkOperation::protocolDetail() const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->protocolDetail; +} + +/*! + Returns the error code for the last error that occurred. +*/ + +int Q3NetworkOperation::errorCode() const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->errorCode; +} + +/*! + \internal +*/ + +QByteArray& Q3NetworkOperation::raw( int num ) const +{ + if ( d->deleteTimer->isActive() ) { + d->deleteTimer->stop(); + d->deleteTimer->start( NETWORK_OP_DELAY ); + } + return d->rawArgs[ num ]; +} + +/*! + Sets this object to delete itself when it hasn't been used for one + second. + + Because Q3NetworkOperation pointers are passed around a lot the + Q3NetworkProtocol generally does not have enough knowledge to + delete these at the correct time. If a Q3NetworkProtocol doesn't + need an operation any more it will call this function instead. + + Note: you should never need to call the method yourself. +*/ + +void Q3NetworkOperation::free() +{ + d->deleteTimer->start( NETWORK_OP_DELAY ); +} + +/*! + \internal + Internal slot for auto-deletion. +*/ + +void Q3NetworkOperation::deleteMe() +{ + delete this; +} + +QT_END_NAMESPACE + +#include "moc_q3networkprotocol.cpp" + +#endif diff --git a/src/qt3support/network/q3networkprotocol.h b/src/qt3support/network/q3networkprotocol.h new file mode 100644 index 0000000..efe2ecf --- /dev/null +++ b/src/qt3support/network/q3networkprotocol.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3NETWORKPROTOCOL_H +#define Q3NETWORKPROTOCOL_H + +#include <QtCore/qstring.h> +#include <QtCore/qobject.h> +#include <Qt3Support/q3dict.h> +#include <Qt3Support/q3valuelist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_NETWORKPROTOCOL + +class Q3NetworkProtocol; +class Q3NetworkOperation; +class QTimer; +class Q3UrlOperator; +class Q3NetworkProtocolPrivate; +class QUrlInfo; +template <class T> class Q3ValueList; + +class Q_COMPAT_EXPORT Q3NetworkProtocolFactoryBase +{ +public: + virtual ~Q3NetworkProtocolFactoryBase() {} + virtual Q3NetworkProtocol *createObject() = 0; +}; + +template< class T > +class Q3NetworkProtocolFactory : public Q3NetworkProtocolFactoryBase +{ +public: + Q3NetworkProtocol *createObject() { + return new T; + } + +}; + +typedef Q3Dict< Q3NetworkProtocolFactoryBase > Q3NetworkProtocolDict; + +class Q_COMPAT_EXPORT Q3NetworkProtocol : public QObject +{ + Q_OBJECT + +public: + enum State { + StWaiting = 0, + StInProgress, + StDone, + StFailed, + StStopped + }; + + enum Operation { + OpListChildren = 1, + OpMkDir = 2, + OpMkdir = OpMkDir, // ### remove in 4.0 + OpRemove = 4, + OpRename = 8, + OpGet = 32, + OpPut = 64 + }; + + enum ConnectionState { + ConHostFound, + ConConnected, + ConClosed + }; + + enum Error { + // no error + NoError = 0, + // general errors + ErrValid, + ErrUnknownProtocol, + ErrUnsupported, + ErrParse, + // errors on connect + ErrLoginIncorrect, + ErrHostNotFound, + // protocol errors + ErrListChildren, + ErrListChlidren = ErrListChildren, // ### remove in 4.0 + ErrMkDir, + ErrMkdir = ErrMkDir, // ### remove in 4.0 + ErrRemove, + ErrRename, + ErrGet, + ErrPut, + ErrFileNotExisting, + ErrPermissionDenied + }; + + Q3NetworkProtocol(); + virtual ~Q3NetworkProtocol(); + + virtual void setUrl( Q3UrlOperator *u ); + + virtual void setAutoDelete( bool b, int i = 10000 ); + bool autoDelete() const; + + static void registerNetworkProtocol( const QString &protocol, + Q3NetworkProtocolFactoryBase *protocolFactory ); + static Q3NetworkProtocol *getNetworkProtocol( const QString &protocol ); + static bool hasOnlyLocalFileSystem(); + + virtual int supportedOperations() const; + virtual void addOperation( Q3NetworkOperation *op ); + + Q3UrlOperator *url() const; + Q3NetworkOperation *operationInProgress() const; + virtual void clearOperationQueue(); + virtual void stop(); + +Q_SIGNALS: + void data( const QByteArray &, Q3NetworkOperation *res ); + void connectionStateChanged( int state, const QString &data ); + void finished( Q3NetworkOperation *res ); + void start( Q3NetworkOperation *res ); + void newChildren( const Q3ValueList<QUrlInfo> &, Q3NetworkOperation *res ); + void newChild( const QUrlInfo &, Q3NetworkOperation *res ); + void createdDirectory( const QUrlInfo &, Q3NetworkOperation *res ); + void removed( Q3NetworkOperation *res ); + void itemChanged( Q3NetworkOperation *res ); + void dataTransferProgress( int bytesDone, int bytesTotal, Q3NetworkOperation *res ); + +protected: + virtual void processOperation( Q3NetworkOperation *op ); + virtual void operationListChildren( Q3NetworkOperation *op ); + virtual void operationMkDir( Q3NetworkOperation *op ); + virtual void operationRemove( Q3NetworkOperation *op ); + virtual void operationRename( Q3NetworkOperation *op ); + virtual void operationGet( Q3NetworkOperation *op ); + virtual void operationPut( Q3NetworkOperation *op ); + virtual void operationPutChunk( Q3NetworkOperation *op ); + virtual bool checkConnection( Q3NetworkOperation *op ); + +private: + Q3NetworkProtocolPrivate *d; + +private Q_SLOTS: + void processNextOperation( Q3NetworkOperation *old ); + void startOps(); + void emitNewChildren( const QUrlInfo &i, Q3NetworkOperation *op ); + + void removeMe(); + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + Q3NetworkProtocol( const Q3NetworkProtocol & ); + Q3NetworkProtocol &operator=( const Q3NetworkProtocol & ); +#endif +}; + +class Q3NetworkOperationPrivate; + +class Q_COMPAT_EXPORT Q3NetworkOperation : public QObject +{ + Q_OBJECT + friend class Q3UrlOperator; + +public: + Q3NetworkOperation( Q3NetworkProtocol::Operation operation, + const QString &arg0, const QString &arg1, + const QString &arg2 ); + Q3NetworkOperation( Q3NetworkProtocol::Operation operation, + const QByteArray &arg0, const QByteArray &arg1, + const QByteArray &arg2 ); + ~Q3NetworkOperation(); + + void setState( Q3NetworkProtocol::State state ); + void setProtocolDetail( const QString &detail ); + void setErrorCode( int ec ); + void setArg( int num, const QString &arg ); + void setRawArg( int num, const QByteArray &arg ); + + Q3NetworkProtocol::Operation operation() const; + Q3NetworkProtocol::State state() const; + QString arg( int num ) const; + QByteArray rawArg( int num ) const; + QString protocolDetail() const; + int errorCode() const; + + void free(); + +private Q_SLOTS: + void deleteMe(); + +private: + QByteArray &raw( int num ) const; + Q3NetworkOperationPrivate *d; + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + Q3NetworkOperation( const Q3NetworkOperation & ); + Q3NetworkOperation &operator=( const Q3NetworkOperation & ); +#endif +}; + +#endif // QT_NO_NETWORKPROTOCOL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3NETWORKPROTOCOL_H diff --git a/src/qt3support/network/q3serversocket.cpp b/src/qt3support/network/q3serversocket.cpp new file mode 100644 index 0000000..f61b652 --- /dev/null +++ b/src/qt3support/network/q3serversocket.cpp @@ -0,0 +1,298 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3serversocket.h" + +#ifndef QT_NO_NETWORK + +#include "qsocketnotifier.h" + +QT_BEGIN_NAMESPACE + +class Q3ServerSocketPrivate { +public: + Q3ServerSocketPrivate(): s(0), n(0) {} + ~Q3ServerSocketPrivate() { delete n; delete s; } + Q3SocketDevice *s; + QSocketNotifier *n; +}; + + +/*! + \class Q3ServerSocket + \brief The Q3ServerSocket class provides a TCP-based server. + + \compat + + This class is a convenience class for accepting incoming TCP + connections. You can specify the port or have Q3ServerSocket pick + one, and listen on just one address or on all the machine's + addresses. + + Using the API is very simple: subclass Q3ServerSocket, call the + constructor of your choice, and implement newConnection() to + handle new incoming connections. There is nothing more to do. + + (Note that due to lack of support in the underlying APIs, + Q3ServerSocket cannot accept or reject connections conditionally.) + + \sa Q3Socket, Q3SocketDevice, QHostAddress, QSocketNotifier +*/ + + +/*! + Creates a server socket object, that will serve the given \a port + on all the addresses of this host. If \a port is 0, Q3ServerSocket + will pick a suitable port in a system-dependent manner. Use \a + backlog to specify how many pending connections the server can + have. + + The \a parent and \a name arguments are passed on to the QObject + constructor. + + \warning On Tru64 Unix systems a value of 0 for \a backlog means + that you don't accept any connections at all; you should specify a + value larger than 0. +*/ + +Q3ServerSocket::Q3ServerSocket( Q_UINT16 port, int backlog, + QObject *parent, const char *name ) + : QObject( parent, name ) +{ + d = new Q3ServerSocketPrivate; + init( QHostAddress(), port, backlog ); +} + + +/*! + Creates a server socket object, that will serve the given \a port + only on the given \a address. Use \a backlog to specify how many + pending connections the server can have. + + The \a parent and \a name arguments are passed on to the QObject + constructor. + + \warning On Tru64 Unix systems a value of 0 for \a backlog means + that you don't accept any connections at all; you should specify a + value larger than 0. +*/ + +Q3ServerSocket::Q3ServerSocket( const QHostAddress & address, Q_UINT16 port, + int backlog, + QObject *parent, const char *name ) + : QObject( parent, name ) +{ + d = new Q3ServerSocketPrivate; + init( address, port, backlog ); +} + + +/*! + Construct an empty server socket. + + This constructor, in combination with setSocket(), allows us to + use the Q3ServerSocket class as a wrapper for other socket types + (e.g. Unix Domain Sockets under Unix). + + The \a parent and \a name arguments are passed on to the QObject + constructor. + + \sa setSocket() +*/ + +Q3ServerSocket::Q3ServerSocket( QObject *parent, const char *name ) + : QObject( parent, name ) +{ + d = new Q3ServerSocketPrivate; +} + + +/*! + Returns true if the construction succeeded; otherwise returns false. +*/ +bool Q3ServerSocket::ok() const +{ + return !!d->s; +} + +/* + The common bit of the constructors. + */ +void Q3ServerSocket::init( const QHostAddress & address, Q_UINT16 port, int backlog ) +{ + d->s = new Q3SocketDevice( Q3SocketDevice::Stream, address.isIPv4Address() + ? Q3SocketDevice::IPv4 : Q3SocketDevice::IPv6, 0 ); +#if !defined(Q_OS_WIN32) + // Under Unix, we want to be able to use the port, even if a socket on the + // same address-port is in TIME_WAIT. Under Windows this is possible anyway + // -- furthermore, the meaning of reusable is different: it means that you + // can use the same address-port for multiple listening sockets. + d->s->setAddressReusable( true ); +#endif + if ( d->s->bind( address, port ) + && d->s->listen( backlog ) ) + { + d->n = new QSocketNotifier( d->s->socket(), QSocketNotifier::Read, + this, "accepting new connections" ); + connect( d->n, SIGNAL(activated(int)), + this, SLOT(incomingConnection(int)) ); + } else { + qWarning( "Q3ServerSocket: failed to bind or listen to the socket" ); + delete d->s; + d->s = 0; + } +} + + +/*! + Destroys the socket. + + This causes any backlogged connections (connections that have + reached the host, but not yet been completely set up by calling + Q3SocketDevice::accept()) to be severed. + + Existing connections continue to exist; this only affects the + acceptance of new connections. +*/ +Q3ServerSocket::~Q3ServerSocket() +{ + delete d; +} + + +/*! + \fn void Q3ServerSocket::newConnection( int socket ) + + This pure virtual function is responsible for setting up a new + incoming connection. \a socket is the fd (file descriptor) for the + newly accepted connection. +*/ + + +void Q3ServerSocket::incomingConnection( int ) +{ + int fd = d->s->accept(); + if ( fd >= 0 ) + newConnection( fd ); +} + + +/*! + Returns the port number on which this server socket listens. This + is always non-zero; if you specify 0 in the constructor, + Q3ServerSocket will pick a non-zero port itself. ok() must be true + before calling this function. + + \sa address() Q3SocketDevice::port() +*/ +Q_UINT16 Q3ServerSocket::port() const +{ + if ( !d || !d->s ) + return 0; + return d->s->port(); +} + + +/*! + Returns the operating system socket. +*/ +int Q3ServerSocket::socket() const +{ + if ( !d || !d->s ) + return -1; + + return d->s->socket(); +} + +/*! + Returns the address on which this object listens, or 0.0.0.0 if + this object listens on more than one address. ok() must be true + before calling this function. + + \sa port() Q3SocketDevice::address() +*/ +QHostAddress Q3ServerSocket::address() const +{ + if ( !d || !d->s ) + return QHostAddress(); + + return d->s->address(); +} + + +/*! + Returns a pointer to the internal socket device. The returned + pointer is 0 if there is no connection or pending connection. + + There is normally no need to manipulate the socket device directly + since this class does all the necessary setup for most client or + server socket applications. +*/ +Q3SocketDevice *Q3ServerSocket::socketDevice() +{ + if ( !d ) + return 0; + + return d->s; +} + + +/*! + Sets the socket to use \a socket. bind() and listen() should + already have been called for \a socket. + + This allows us to use the Q3ServerSocket class as a wrapper for + other socket types (e.g. Unix Domain Sockets). +*/ +void Q3ServerSocket::setSocket( int socket ) +{ + delete d; + d = new Q3ServerSocketPrivate; + d->s = new Q3SocketDevice( socket, Q3SocketDevice::Stream ); + d->n = new QSocketNotifier( d->s->socket(), QSocketNotifier::Read, + this, "accepting new connections" ); + connect( d->n, SIGNAL(activated(int)), + this, SLOT(incomingConnection(int)) ); +} + +QT_END_NAMESPACE + +#endif //QT_NO_NETWORK diff --git a/src/qt3support/network/q3serversocket.h b/src/qt3support/network/q3serversocket.h new file mode 100644 index 0000000..e26e1a5 --- /dev/null +++ b/src/qt3support/network/q3serversocket.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3SERVERSOCKET_H +#define Q3SERVERSOCKET_H + +#include <QtCore/qobject.h> +#include <QtNetwork/qhostaddress.h> +#include <Qt3Support/q3socketdevice.h> // ### remove or keep for users' convenience? + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +class Q3ServerSocketPrivate; + +class Q_COMPAT_EXPORT Q3ServerSocket : public QObject +{ + Q_OBJECT +public: + Q3ServerSocket( Q_UINT16 port, int backlog = 1, + QObject *parent=0, const char *name=0 ); + Q3ServerSocket( const QHostAddress & address, Q_UINT16 port, int backlog = 1, + QObject *parent=0, const char *name=0 ); + Q3ServerSocket( QObject *parent=0, const char *name=0 ); + virtual ~Q3ServerSocket(); + + bool ok() const; + + Q_UINT16 port() const ; + + int socket() const ; + virtual void setSocket( int socket ); + + QHostAddress address() const ; + + virtual void newConnection( int socket ) = 0; + +protected: + Q3SocketDevice *socketDevice(); + +private Q_SLOTS: + void incomingConnection( int socket ); + +private: + Q3ServerSocketPrivate *d; + void init( const QHostAddress & address, Q_UINT16 port, int backlog ); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3SERVERSOCKET_H diff --git a/src/qt3support/network/q3socket.cpp b/src/qt3support/network/q3socket.cpp new file mode 100644 index 0000000..01585f0 --- /dev/null +++ b/src/qt3support/network/q3socket.cpp @@ -0,0 +1,1518 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3socket.h" +#ifndef QT_NO_NETWORK +#include "q3ptrlist.h" +#include "qtimer.h" +#include "q3socketdevice.h" +#include "q3dns.h" +#include "private/q3membuf_p.h" + +#include <string.h> +#ifndef NO_ERRNO_H +#if defined(Q_OS_WINCE) +#include "qfunctions_wince.h" +#else +#include <errno.h> +#endif +#endif + +QT_BEGIN_NAMESPACE + +//#define Q3SOCKET_DEBUG + +/* + Perhaps this private functionality needs to be refactored. + + Comment from Robert D Gatlin (Intel): + + It would be nice to have the functionality inherent in Q3Socket available + as a separate class as a standard part of the Qt library, something along + the line of: + + class QByteBuffer : public QIODevice { ... } + + The same class could/would be used within Q3Socket for the Read/Write + buffers. + + The above class could be used in the following way(s): + + buffer.open( IO_WriteOnly | IO_Append ); + buffer.writeBlock( a ); // a = QByteArray + buffer.close(); + + QByteArray b; + b.resize( buffer.size() ); + buffer.open( IO_ReadOnly ); + buffer.readBlock( b.data(), b.size() ); + buffer.close(); + + But would also be useable with QDataStream (via QIODevice) with: + + buffer.open( IO_WriteOnly | IO_Append ); + QDataStream is( &buffer ); + is << 100; + buffer.close(); + + buffer.open( IO_ReadOnly ); + QDataStream os( &buffer ); + Q_UINT32 x; + os >> x; + buffer.close(); + + The real usefulness is with any situations where data (QByteArray) arrives + incrementally (as in Q3Socket and filter case above). + + I tried using QBuffer, but QBuffer does not trim bytes from the front of + the buffer in cases like: + + QBuffer buf; + buf.open( IO_ReadOnly ); + QDataStream ds( &buf ); + Q_INT32 x; + ds >> x; + buf.close(); + + In the above case, buf.size() will be identical before and after the + operation with QDataStream. Based on the implementation of QBuffer, it + does not appear well suited for this kind of operation. +*/ + +// Private class for Q3Socket + +class Q3SocketPrivate { +public: + Q3SocketPrivate(); + ~Q3SocketPrivate(); + void closeSocket(); + void close(); + void connectionClosed(); + void setSocketDevice( Q3Socket *q, Q3SocketDevice *device ); + + Q3Socket::State state; // connection state + QString host; // host name + Q_UINT16 port; // host port + Q3SocketDevice *socket; // connection socket + QSocketNotifier *rsn, *wsn; // socket notifiers + Q3Membuf rba; // read buffer + Q_ULONG readBufferSize; // limit for the read buffer size + Q3PtrList<QByteArray> wba; // list of write bufs + QHostAddress addr; // connection address + Q3ValueList<QHostAddress> addresses; // alternatives looked up + QIODevice::Offset wsize; // write total buf size + QIODevice::Offset windex; // write index +#ifndef QT_NO_DNS + Q3Dns *dns4; + Q3Dns *dns6; +#endif + static Q3PtrList<Q3Socket> sn_read_alreadyCalled; // used to avoid unwanted recursion + Q3ValueList<QHostAddress> l4; + Q3ValueList<QHostAddress> l6; +}; + +Q3PtrList<Q3Socket> Q3SocketPrivate::sn_read_alreadyCalled; + +Q3SocketPrivate::Q3SocketPrivate() + : state(Q3Socket::Idle), host(QString::fromLatin1("")), port(0), + socket(0), rsn(0), wsn(0), readBufferSize(0), wsize(0), windex(0) +{ +#ifndef QT_NO_DNS + dns4 = 0; + dns6 = 0; +#endif + wba.setAutoDelete( true ); +} + +Q3SocketPrivate::~Q3SocketPrivate() +{ + close(); + delete socket; +#ifndef QT_NO_DNS + delete dns4; + delete dns6; +#endif +} + +extern void qDeleteInEventHandler(QObject *o); +void Q3SocketPrivate::closeSocket() +{ + // Order is important here - the socket notifiers must go away + // before the socket does, otherwise libc or the kernel will + // become unhappy. + if (rsn) { + qDeleteInEventHandler(rsn); + rsn = 0; + } + if (wsn) { + qDeleteInEventHandler(wsn); + wsn = 0; + } + if ( socket ) + socket->close(); +} + +void Q3SocketPrivate::close() +{ + closeSocket(); + wsize = 0; + rba.clear(); wba.clear(); + windex = 0; +} + +void Q3SocketPrivate::connectionClosed() +{ + // We keep the open state in case there's unread incoming data + state = Q3Socket::Idle; + closeSocket(); + wba.clear(); + windex = wsize = 0; +} + +void Q3SocketPrivate::setSocketDevice( Q3Socket *q, Q3SocketDevice *device ) +{ + delete socket; + delete rsn; + delete wsn; + + if ( device ) { + socket = device; + } else { + socket = new Q3SocketDevice( Q3SocketDevice::Stream, + ( addr.isIPv4Address() ? + Q3SocketDevice::IPv4 : + Q3SocketDevice::IPv6 ), 0 ); + socket->setBlocking( false ); + socket->setAddressReusable( true ); + } + + rsn = new QSocketNotifier( socket->socket(), + QSocketNotifier::Read, q, "read" ); + wsn = new QSocketNotifier( socket->socket(), + QSocketNotifier::Write, q, "write" ); + + QObject::connect( rsn, SIGNAL(activated(int)), q, SLOT(sn_read()) ); + rsn->setEnabled( false ); + QObject::connect( wsn, SIGNAL(activated(int)), q, SLOT(sn_write()) ); + wsn->setEnabled( false ); +} + +/*! + \class Q3Socket + \brief The Q3Socket class provides a buffered TCP connection. + + \compat + + It provides a totally non-blocking QIODevice, and modifies and + extends the API of QIODevice with socket-specific code. + + The functions you're likely to call most are connectToHost(), + bytesAvailable(), canReadLine() and the ones it inherits from + QIODevice. + + connectToHost() is the most-used function. As its name implies, + it opens a connection to a named host. + + Most network protocols are either packet-oriented or + line-oriented. canReadLine() indicates whether a connection + contains an entire unread line or not, and bytesAvailable() + returns the number of bytes available for reading. + + The signals error(), connected(), readyRead() and + connectionClosed() inform you of the progress of the connection. + There are also some less commonly used signals. hostFound() is + emitted when connectToHost() has finished its DNS lookup and is + starting its TCP connection. delayedCloseFinished() is emitted + when close() succeeds. bytesWritten() is emitted when Q3Socket + moves data from its "to be written" queue into the TCP + implementation. + + There are several access functions for the socket: state() returns + whether the object is idle, is doing a DNS lookup, is connecting, + has an operational connection, etc. address() and port() return + the IP address and port used for the connection. The peerAddress() + and peerPort() functions return the IP address and port used by + the peer, and peerName() returns the name of the peer (normally + the name that was passed to connectToHost()). socketDevice() + returns a pointer to the Q3SocketDevice used for this socket. + + Q3Socket inherits QIODevice, and reimplements some functions. In + general, you can treat it as a QIODevice for writing, and mostly + also for reading. The match isn't perfect, since the QIODevice + API is designed for devices that are controlled by the same + machine, and an asynchronous peer-to-peer network connection isn't + quite like that. For example, there is nothing that matches + QIODevice::size() exactly. The documentation for open(), close(), + flush(), size(), at(), atEnd(), readBlock(), writeBlock(), + getch(), putch(), ungetch() and readLine() describes the + differences in detail. + + \warning Q3Socket is not suitable for use in threads. If you need + to uses sockets in threads use the lower-level Q3SocketDevice class. + + \sa Q3SocketDevice, QHostAddress, QSocketNotifier +*/ + + +/*! + Creates a Q3Socket object in Q3Socket::Idle state. + + The \a parent and \a name arguments are passed on to the QObject + constructor. +*/ + +Q3Socket::Q3Socket( QObject *parent, const char *name ) + : QIODevice( parent ) +{ + setObjectName(QLatin1String(name)); + d = new Q3SocketPrivate; + setSocketDevice( 0 ); + resetStatus(); +} + + +/*! + Destroys the socket. Closes the connection if necessary. + + \sa close() +*/ + +Q3Socket::~Q3Socket() +{ +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): Destroy", name() ); +#endif + if ( state() != Idle ) + close(); + Q_ASSERT( d != 0 ); + delete d; +} + + +/*! + Returns a pointer to the internal socket device. + + There is normally no need to manipulate the socket device directly + since this class does the necessary setup for most applications. +*/ + +Q3SocketDevice *Q3Socket::socketDevice() +{ + return d->socket; +} + +/*! + Sets the internal socket device to \a device. Passing a \a device + of 0 will cause the internal socket device to be used. Any + existing connection will be disconnected before using the new \a + device. + + The new device should not be connected before being associated + with a Q3Socket; after setting the socket call connectToHost() to + make the connection. + + This function is useful if you need to subclass Q3SocketDevice and + want to use the Q3Socket API, for example, to implement Unix domain + sockets. +*/ + +void Q3Socket::setSocketDevice( Q3SocketDevice *device ) +{ + if ( state() != Idle ) + close(); + d->setSocketDevice( this, device ); +} + +/*! + \enum Q3Socket::State + + This enum defines the connection states: + + \value Idle if there is no connection + \value HostLookup during a DNS lookup + \value Connecting during TCP connection establishment + \value Connected when there is an operational connection + \value Closing if the socket is closing down, but is not yet closed. + \omitvalue Connection +*/ + +/*! + Returns the current state of the socket connection. + + \sa Q3Socket::State +*/ + +Q3Socket::State Q3Socket::state() const +{ + return d->state; +} + + +#ifndef QT_NO_DNS + +/*! + Attempts to make a connection to \a host on the specified \a port + and return immediately. + + Any connection or pending connection is closed immediately, and + Q3Socket goes into the \c HostLookup state. When the lookup + succeeds, it emits hostFound(), starts a TCP connection and goes + into the \c Connecting state. Finally, when the connection + succeeds, it emits connected() and goes into the \c Connected + state. If there is an error at any point, it emits error(). + + \a host may be an IP address in string form, or it may be a DNS + name. Q3Socket will do a normal DNS lookup if required. Note that + \a port is in native byte order, unlike some other libraries. + + \sa state() +*/ + +void Q3Socket::connectToHost( const QString &host, Q_UINT16 port ) +{ +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s)::connectToHost: host %s, port %d", + name(), host.ascii(), port ); +#endif + setSocketIntern( -1 ); + d->state = HostLookup; + d->host = host; + d->port = port; + d->dns4 = new Q3Dns( host, Q3Dns::A ); + d->dns6 = new Q3Dns( host, Q3Dns::Aaaa ); + + // try if the address is already available (for faster connecting...) + tryConnecting(); + if ( d->state == HostLookup ) { + connect( d->dns4, SIGNAL(resultsReady()), + this, SLOT(tryConnecting()) ); + connect( d->dns6, SIGNAL(resultsReady()), + this, SLOT(tryConnecting()) ); + } +} + +#endif + + +/*! + This private slots continues the connection process where + connectToHost() leaves off. +*/ + +void Q3Socket::tryConnecting() +{ +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s)::tryConnecting()", name() ); +#endif + // ### this ifdef isn't correct - addresses() also does /etc/hosts and + // numeric-address-as-string handling. +#ifndef QT_NO_DNS + + if ( d->dns4 ) { + d->l4 = d->dns4->addresses(); + if ( !d->l4.isEmpty() || !d->dns4->isWorking() ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s)::tryConnecting: host %s, port %d: " + "%d IPv4 addresses", + name(), d->host.ascii(), d->port, d->l4.count() ); +#endif + delete d->dns4; + d->dns4 = 0; + } + } + + if ( d->dns6 ) { + d->l6 = d->dns6->addresses(); + if ( !d->l6.isEmpty() || !d->dns6->isWorking() ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s)::tryConnecting: host %s, port %d: " + "%d IPv6 addresses", + name(), d->host.ascii(), d->port, d->l6.count() ); +#endif + delete d->dns6; + d->dns6 = 0; + } + } + + if ( d->state == HostLookup ) { + if ( d->l4.isEmpty() && d->l6.isEmpty() && + !d->dns4 && !d->dns6 ) { + // no results and we're not still looking: give up + d->state = Idle; + emit error( ErrHostNotFound ); + return; + } + if ( d->l4.isEmpty() && d->l6.isEmpty() ) { + // no results (yet): try again later + return; + } + + // we've found something. press on with that. if we later find + // more, fine. + emit hostFound(); + d->state = Connecting; + } + + if ( d->state == Connecting ) { + d->addresses += d->l4; + d->addresses += d->l6; + d->l4.clear(); + d->l6.clear(); + + // try one address at a time, falling back to the next one if + // there is a connection failure. (should also support a timeout, + // or do multiple TCP-level connects at a time, with staggered + // starts to avoid bandwidth waste and cause fewer + // "connect-and-abort" errors. but that later.) + bool stuck = true; + while( stuck ) { + stuck = false; + if ( d->socket && + d->socket->connect( d->addr, d->port ) == false ) { + if ( d->socket->error() == Q3SocketDevice::NoError ) { + if ( d->wsn ) + d->wsn->setEnabled( true ); + return; // not serious, try again later + } + +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s)::tryConnecting: " + "Gave up on IP address %s", + name(), d->socket->peerAddress().toString().ascii() ); +#endif + delete d->wsn; + d->wsn = 0; + delete d->rsn; + d->rsn = 0; + delete d->socket; + d->socket = 0; + + if(d->addresses.isEmpty()) { + emit error( ErrConnectionRefused ); + return; + } + } + // if the host has more addresses, try another some. + if ( d->socket == 0 && !d->addresses.isEmpty() ) { + d->addr = *d->addresses.begin(); + d->addresses.remove( d->addresses.begin() ); + d->setSocketDevice( this, 0 ); + stuck = true; +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s)::tryConnecting: Trying IP address %s", + name(), d->addr.toString().ascii() ); +#endif + } + }; + + // The socket write notifier will fire when the connection succeeds + if ( d->wsn ) + d->wsn->setEnabled( true ); + } +#endif +} + +/*! + \enum Q3Socket::Error + + This enum specifies the possible errors: + \value ErrConnectionRefused if the connection was refused + \value ErrHostNotFound if the host was not found + \value ErrSocketRead if a read from the socket failed +*/ + +/*! + \fn void Q3Socket::error(int error) + + This signal is emitted after an error occurred. The \a error parameter is + the \l Error value. +*/ + +/*! + \fn void Q3Socket::hostFound() + + This signal is emitted after connectToHost() has been called and + the host lookup has succeeded. + + \sa connected() +*/ + + +/*! + \fn void Q3Socket::connected() + + This signal is emitted after connectToHost() has been called and a + connection has been successfully established. + + \sa connectToHost(), connectionClosed() +*/ + + +/*! + \fn void Q3Socket::connectionClosed() + + This signal is emitted when the other end has closed the + connection. The read buffers may contain buffered input data which + you can read after the connection was closed. + + \sa connectToHost(), close() +*/ + + +/*! + \fn void Q3Socket::delayedCloseFinished() + + This signal is emitted when a delayed close is finished. + + If you call close() and there is buffered output data to be + written, Q3Socket goes into the Q3Socket::Closing state and + returns immediately. It will then keep writing to the socket until + all the data has been written. Then, the delayedCloseFinished() + signal is emitted. + + \sa close() +*/ + + +/*! + \fn void Q3Socket::readyRead() + + This signal is emitted every time there is new incoming data. + + Bear in mind that new incoming data is only reported once; if you do not + read all the data, this class buffers the data and you can read it later, + but no signal is emitted unless new data arrives. A good practice is to + read all data in the slot connected to this signal unless you are sure that + you need to receive more data to be able to process it. + + \sa readBlock(), readLine(), bytesAvailable() +*/ + + +/*! + \fn void Q3Socket::bytesWritten( int nbytes ) + + This signal is emitted when data has been written to the network. + The \a nbytes parameter specifies how many bytes were written. + + The bytesToWrite() function is often used in the same context; it + indicates how many buffered bytes there are left to write. + + \sa writeBlock(), bytesToWrite() +*/ + + +/*! + Opens the socket using the specified QIODevice file mode \a m. + This function is called automatically when needed and you should + not call it yourself. + + \sa close() +*/ + +bool Q3Socket::open( OpenMode m ) +{ + if ( isOpen() ) { +#if defined(QT_CHECK_STATE) + qWarning( "Q3Socket::open: Already open" ); +#endif + return false; + } + QIODevice::setOpenMode( m & ReadWrite ); + return true; +} + +/*! + \fn bool Q3Socket::open(int m) + \overload +*/ + +/*! + Closes the socket. + + The read buffer is cleared. + + If the output buffer is empty, the state is set to \c + Q3Socket::Idle and the connection is terminated immediately. If the + output buffer still contains data to be written, Q3Socket goes into + the Q3Socket::Closing state and the rest of the data will be + written. When all of the outgoing data have been written, the + state is set to Q3Socket::Idle and the connection is terminated. + At this point, the delayedCloseFinished() signal is emitted. + + If you don't want that the data of the output buffer is written, call + clearPendingData() before you call close(). + + \sa state(), bytesToWrite() clearPendingData() +*/ + +void Q3Socket::close() +{ + if ( !isOpen() || d->state == Idle ) // already closed + return; + if ( d->state == Closing ) + return; + if ( !d->rsn || !d->wsn ) + return; +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): close socket", name() ); +#endif + if ( d->socket && d->wsize ) { // there's data to be written + d->state = Closing; + if ( d->rsn ) + d->rsn->setEnabled( false ); + if ( d->wsn ) + d->wsn->setEnabled( true ); + d->rba.clear(); // clear incoming data + return; + } + resetStatus(); + setOpenMode(NotOpen); + d->close(); + d->state = Idle; +} + + +/*! + This function consumes \a nbytes bytes of data from the write + buffer. +*/ + +bool Q3Socket::consumeWriteBuf( Q_ULONG nbytes ) +{ + if ( nbytes <= 0 || (qint64)nbytes > d->wsize ) + return false; +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): skipWriteBuf %d bytes", name(), (int)nbytes ); +#endif + d->wsize -= nbytes; + for ( ;; ) { + QByteArray *a = d->wba.first(); + if ( (qint64)(d->windex + nbytes) >= a->size() ) { + nbytes -= a->size() - d->windex; + d->wba.remove(); + d->windex = 0; + if ( nbytes == 0 ) + break; + } else { + d->windex += nbytes; + break; + } + } + return true; +} + + + +/*! + Implementation of the abstract virtual QIODevice::flush() function. + This function always returns true. +*/ + +bool Q3Socket::flush() +{ + if ( !d->socket ) + return true; + bool osBufferFull = false; + int consumed = 0; + while ( !osBufferFull && d->state >= Connecting && d->wsize > 0 ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): flush: Write data to the socket", name() ); +#endif + QByteArray *a = d->wba.first(); + int nwritten; + int i = 0; + if ( (int)a->size() - d->windex < 1460 ) { + // Concatenate many smaller blocks. the first may be + // partial, but each subsequent block is copied entirely + // or not at all. the sizes here are picked so that we + // generally won't trigger nagle's algorithm in the tcp + // implementation: we concatenate if we'd otherwise send + // less than PMTU bytes (we assume PMTU is 1460 bytes), + // and concatenate up to the largest payload TCP/IP can + // carry. with these precautions, nagle's algorithm + // should apply only when really appropriate. + QByteArray out( 65536 ); + int j = d->windex; + int s = a->size() - j; + while ( a && i+s < (int)out.size() ) { + memcpy( out.data()+i, a->data()+j, s ); + j = 0; + i += s; + a = d->wba.next(); + s = a ? a->size() : 0; + } + nwritten = d->socket->write( out.data(), i ); + if ( d->wsn ) + d->wsn->setEnabled( false ); // the QSocketNotifier documentation says so + } else { + // Big block, write it immediately + i = a->size() - d->windex; + nwritten = d->socket->write( a->data() + d->windex, i ); + if ( d->wsn ) + d->wsn->setEnabled( false ); // the QSocketNotifier documentation says so + } + if ( nwritten > 0 ) { + if ( consumeWriteBuf( nwritten ) ) + consumed += nwritten; + } + if ( nwritten < i ) + osBufferFull = true; + } + if ( consumed > 0 ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): flush: wrote %d bytes, %d left", + name(), consumed, (int)d->wsize ); +#endif + emit bytesWritten( consumed ); + } + if ( d->state == Closing && d->wsize == 0 ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): flush: Delayed close done. Terminating.", + name() ); +#endif + resetStatus(); + setOpenMode(NotOpen); + d->close(); + d->state = Idle; + emit delayedCloseFinished(); + return true; + } + if ( !d->socket->isOpen() ) { + d->connectionClosed(); + emit connectionClosed(); + return true; + } + if ( d->wsn ) + d->wsn->setEnabled( d->wsize > 0 ); // write if there's data + return true; +} + + +/*! + Returns the number of incoming bytes that can be read right now + (like bytesAvailable()). +*/ + +QIODevice::Offset Q3Socket::size() const +{ + return (Offset)bytesAvailable(); +} + + +/*! + Returns the current read index. Since Q3Socket is a sequential + device, the current read index is always zero. +*/ + +QIODevice::Offset Q3Socket::at() const +{ + return 0; +} + + +/*! + \overload + + Moves the read index forward to \a index and returns true if the + operation was successful; otherwise returns false. Moving the + index forward means skipping incoming data. +*/ + +bool Q3Socket::at( Offset index ) +{ + if ( index > d->rba.size() ) + return false; + d->rba.consumeBytes( (Q_ULONG)index, 0 ); // throw away data 0..index-1 + // After we read data from our internal buffer, if we use the + // setReadBufferSize() to limit our buffer, we might now be able to + // read more data in our buffer. So enable the read socket notifier, + // but do this only if we are not in a slot connected to the + // readyRead() signal since this might cause a bad recursive behavior. + // We can test for this condition by looking at the + // sn_read_alreadyCalled flag. + if ( d->rsn && Q3SocketPrivate::sn_read_alreadyCalled.findRef(this) == -1 ) + d->rsn->setEnabled( true ); + return true; +} + + +/*! + Returns true if there is no more data to read; otherwise returns false. +*/ + +bool Q3Socket::atEnd() const +{ + if ( d->socket == 0 ) + return true; + Q3Socket * that = (Q3Socket *)this; + if ( that->d->socket->bytesAvailable() ) // a little slow, perhaps... + that->sn_read(); + return that->d->rba.size() == 0; +} + + +/*! + Returns the number of incoming bytes that can be read, i.e. the + size of the input buffer. Equivalent to size(). + + \sa bytesToWrite() +*/ + +qint64 Q3Socket::bytesAvailable() const +{ + if ( d->socket == 0 ) + return 0; + Q3Socket * that = (Q3Socket *)this; + if ( that->d->socket->bytesAvailable() ) // a little slow, perhaps... + (void)that->sn_read(); + return that->d->rba.size() + QIODevice::bytesAvailable(); +} + + +/*! + Wait up to \a msecs milliseconds for more data to be available. + + If \a msecs is -1 the call will block indefinitely. + + Returns the number of bytes available. + + If \a timeout is non-null and no error occurred (i.e. it does not + return -1): this function sets *\a timeout to true, if the reason + for returning was that the timeout was reached; otherwise it sets + *\a timeout to false. This is useful to find out if the peer + closed the connection. + + \warning This is a blocking call and should be avoided in event + driven applications. + + \sa bytesAvailable() +*/ + +Q_ULONG Q3Socket::waitForMore( int msecs, bool *timeout ) const +{ + if ( d->socket == 0 ) + return 0; + Q3Socket * that = (Q3Socket *)this; + if ( that->d->socket->waitForMore( msecs, timeout ) > 0 ) + (void)that->sn_read( true ); + return that->d->rba.size(); +} + +/*! \overload +*/ + +Q_ULONG Q3Socket::waitForMore( int msecs ) const +{ + return waitForMore( msecs, 0 ); +} + +/*! + Returns the number of bytes that are waiting to be written, i.e. + the size of the output buffer. + + \sa bytesAvailable() clearPendingData() +*/ + +qint64 Q3Socket::bytesToWrite() const +{ + return d->wsize; +} + +/*! + Deletes the data that is waiting to be written. This is useful if you want + to close the socket without waiting for all the data to be written. + + \sa bytesToWrite() close() delayedCloseFinished() +*/ + +void Q3Socket::clearPendingData() +{ + d->wba.clear(); + d->windex = d->wsize = 0; +} + +/*! + Reads \a maxlen bytes from the socket into \a data and returns the + number of bytes read. Returns -1 if an error occurred. +*/ + +qint64 Q3Socket::readData( char *data, qint64 maxlen ) +{ + if ( data == 0 && maxlen != 0 ) { +#if defined(QT_CHECK_NULL) + qWarning( "Q3Socket::readBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) + qWarning( "Q3Socket::readBlock: Socket is not open" ); +#endif + return -1; + } + if ( maxlen >= d->rba.size() ) + maxlen = d->rba.size(); +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): readBlock %d bytes", name(), (int)maxlen ); +#endif + d->rba.consumeBytes( maxlen, data ); + // After we read data from our internal buffer, if we use the + // setReadBufferSize() to limit our buffer, we might now be able to + // read more data in our buffer. So enable the read socket notifier, + // but do this only if we are not in a slot connected to the + // readyRead() signal since this might cause a bad recursive behavior. + // We can test for this condition by looking at the + // sn_read_alreadyCalled flag. + if ( d->rsn && Q3SocketPrivate::sn_read_alreadyCalled.findRef(this) == -1 ) + d->rsn->setEnabled( true ); + return maxlen; +} + + +/*! + Writes \a len bytes to the socket from \a data and returns the + number of bytes written. Returns -1 if an error occurred. +*/ + +qint64 Q3Socket::writeData( const char *data, qint64 len ) +{ +#if defined(QT_CHECK_NULL) + if ( data == 0 && len != 0 ) { + qWarning( "Q3Socket::writeBlock: Null pointer error" ); + } +#endif +#if defined(QT_CHECK_STATE) + if ( !isOpen() ) { + qWarning( "Q3Socket::writeBlock: Socket is not open" ); + return -1; + } +#endif +#if defined(QT_CHECK_STATE) + if ( d->state == Closing ) { + qWarning( "Q3Socket::writeBlock: Cannot write, socket is closing" ); + } +#endif + if ( len == 0 || d->state == Closing || d->state == Idle ) + return 0; + QByteArray *a = d->wba.last(); + + // next bit is sensitive. if we're writing really small chunks, + // try to buffer up since system calls are expensive, and nagle's + // algorithm is even more expensive. but if anything even + // remotely large is being written, try to issue a write at once. + + bool writeNow = ( d->wsize + len >= 1400 || len > 512 ); + + if ( a && a->size() + len < 128 ) { + // small buffer, resize + int i = a->size(); + a->resize( i+len ); + memcpy( a->data()+i, data, len ); + } else { + // append new buffer + a = new QByteArray( len ); + memcpy( a->data(), data, len ); + d->wba.append( a ); + } + d->wsize += len; + if ( writeNow ) + flush(); + else if ( d->wsn ) + d->wsn->setEnabled( true ); +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): writeBlock %d bytes", name(), (int)len ); +#endif + return len; +} + + +/*! + Reads a single byte/character from the internal read buffer. + Returns the byte/character read, or -1 if there is nothing to be + read. + + \sa bytesAvailable(), putch() +*/ + +int Q3Socket::getch() +{ + if ( isOpen() && d->rba.size() > 0 ) { + uchar c; + d->rba.consumeBytes( 1, (char*)&c ); + // After we read data from our internal buffer, if we use the + // setReadBufferSize() to limit our buffer, we might now be able to + // read more data in our buffer. So enable the read socket notifier, + // but do this only if we are not in a slot connected to the + // readyRead() signal since this might cause a bad recursive behavior. + // We can test for this condition by looking at the + // sn_read_alreadyCalled flag. + if ( d->rsn && Q3SocketPrivate::sn_read_alreadyCalled.findRef(this) == -1 ) + d->rsn->setEnabled( true ); + return c; + } + return -1; +} + + +/*! + Writes the character \a ch to the output buffer. + + Returns \a ch, or -1 if an error occurred. + + \sa getch() +*/ + +int Q3Socket::putch( int ch ) +{ + char buf[2]; + buf[0] = ch; + return writeBlock(buf, 1) == 1 ? ch : -1; +} + + +/*! + This implementation of the virtual function QIODevice::ungetch() + prepends the character \a ch to the read buffer so that the next + read returns this character as the first character of the output. +*/ + +int Q3Socket::ungetch( int ch ) +{ +#if defined(QT_CHECK_STATE) + if ( !isOpen() ) { + qWarning( "Q3Socket::ungetch: Socket not open" ); + return -1; + } +#endif + return d->rba.ungetch( ch ); +} + + +/*! + Returns true if it's possible to read an entire line of text from + this socket at this time; otherwise returns false. + + Note that if the peer closes the connection unexpectedly, this + function returns false. This means that loops such as this won't + work: + + \snippet doc/src/snippets/code/src_qt3support_network_q3socket.cpp 0 + + \sa readLine() +*/ + +bool Q3Socket::canReadLine() const +{ + if ( ((Q3Socket*)this)->d->rba.scanNewline( 0 ) ) + return true; + return ( bytesAvailable() > 0 && + (((Q3Socket*)this)->d->rba.scanNewline( 0 ) || QIODevice::canReadLine()) ); +} + +/*! + \internal + Internal slot for handling socket read notifications. + + This function has can usually only be entered once (i.e. no + recursive calls). If the argument \a force is true, the function + is executed, but no readyRead() signals are emitted. This + behaviour is useful for the waitForMore() function, so that it is + possible to call waitForMore() in a slot connected to the + readyRead() signal. +*/ + +void Q3Socket::sn_read( bool force ) +{ + Q_LONG maxToRead = 0; + if ( d->readBufferSize > 0 ) { + maxToRead = d->readBufferSize - d->rba.size(); + if ( maxToRead <= 0 ) { + if ( d->rsn ) + d->rsn->setEnabled( false ); + return; + } + } + + // Use Q3SocketPrivate::sn_read_alreadyCalled to avoid recursive calls of + // sn_read() (and as a result avoid emitting the readyRead() signal in a + // slot for readyRead(), if you use bytesAvailable()). + if ( !force && Q3SocketPrivate::sn_read_alreadyCalled.findRef(this) != -1 ) + return; + Q3SocketPrivate::sn_read_alreadyCalled.append( this ); + + char buf[4096]; + Q_LONG nbytes = d->socket->bytesAvailable(); + Q_LONG nread; + QByteArray *a = 0; + + if ( state() == Connecting ) { + if ( nbytes > 0 ) { + tryConnection(); + } else { + // nothing to do, nothing to care about + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + } + if ( state() == Idle ) { + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + + if ( nbytes <= 0 ) { // connection closed? + // On Windows this may happen when the connection is still open. + // This happens when the system is heavily loaded and we have + // read all the data on the socket before a new WSAAsyncSelect + // event is processed. A new read operation would then block. + // This code is also useful when Q3Socket is used without an + // event loop. + nread = d->socket->readBlock( buf, maxToRead ? QMIN((Q_LONG)sizeof(buf),maxToRead) : sizeof(buf) ); + if ( nread == 0 ) { // really closed + if ( !d->socket->isOpen() ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): sn_read: Connection closed", name() ); +#endif + d->connectionClosed(); + emit connectionClosed(); + } + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } else { + if ( nread < 0 ) { + if ( d->socket->error() == Q3SocketDevice::NoError ) { + // all is fine + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } +#if defined(Q3SOCKET_DEBUG) + qWarning( "Q3Socket::sn_read (%s): Close error", name() ); +#endif + if ( d->rsn ) + d->rsn->setEnabled( false ); + emit error( ErrSocketRead ); + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + a = new QByteArray( nread ); + memcpy( a->data(), buf, nread ); + } + + } else { // data to be read +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): sn_read: %ld incoming bytes", name(), nbytes ); +#endif + if ( nbytes > (int)sizeof(buf) ) { + // big + a = new QByteArray( nbytes ); + nread = d->socket->readBlock( a->data(), maxToRead ? QMIN(nbytes,maxToRead) : nbytes ); + } else { + a = 0; + nread = d->socket->readBlock( buf, maxToRead ? QMIN((Q_LONG)sizeof(buf),maxToRead) : sizeof(buf) ); + if ( nread > 0 ) { + // ##### could setRawData + a = new QByteArray( nread ); + memcpy( a->data(), buf, nread ); + } + } + if ( nread == 0 ) { +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): sn_read: Connection closed", name() ); +#endif + // ### we should rather ask the socket device if it is closed + d->connectionClosed(); + emit connectionClosed(); + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + delete a; + return; + } else if ( nread < 0 ) { + delete a; + + if ( d->socket->error() == Q3SocketDevice::NoError ) { + // all is fine + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } +#if defined(QT_CHECK_RANGE) + qWarning( "Q3Socket::sn_read: Read error" ); +#endif + if ( d->rsn ) + d->rsn->setEnabled( false ); + emit error( ErrSocketRead ); + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + if ( nread != (int)a->size() ) { // unexpected +#if defined(CHECK_RANGE) && !defined(Q_OS_WIN32) + qWarning( "Q3Socket::sn_read: Unexpected short read" ); +#endif + a->resize( nread ); + } + } + d->rba.append( a ); + if ( !force ) { + if ( d->rsn ) + d->rsn->setEnabled( false ); + emit readyRead(); + if ( d->rsn ) + d->rsn->setEnabled( true ); + } + + Q3SocketPrivate::sn_read_alreadyCalled.removeRef( this ); +} + + +/*! + \internal + Internal slot for handling socket write notifications. +*/ + +void Q3Socket::sn_write() +{ + if ( d->state == Connecting ) // connection established? + tryConnection(); + flush(); +} + +void Q3Socket::emitErrorConnectionRefused() +{ + emit error( ErrConnectionRefused ); +} + +void Q3Socket::tryConnection() +{ + if ( d->socket->connect( d->addr, d->port ) ) { + d->state = Connected; +#if defined(Q3SOCKET_DEBUG) + qDebug( "Q3Socket (%s): sn_write: Got connection to %s", + name(), peerName().ascii() ); +#endif + if ( d->rsn ) + d->rsn->setEnabled( true ); + emit connected(); + } else { + d->state = Idle; + QTimer::singleShot( 0, this, SLOT(emitErrorConnectionRefused()) ); + return; + } +} + + +/*! + Returns the socket number, or -1 if there is no socket at the moment. +*/ + +int Q3Socket::socket() const +{ + if ( d->socket == 0 ) + return -1; + return d->socket->socket(); +} + +/*! + Sets the socket to use \a socket and the state() to \c Connected. + The socket must already be connected. + + This allows us to use the Q3Socket class as a wrapper for other + socket types (e.g. Unix Domain Sockets). +*/ + +void Q3Socket::setSocket( int socket ) +{ + setSocketIntern( socket ); + d->state = Connection; + d->rsn->setEnabled( true ); +} + + +/*! + Sets the socket to \a socket. This is used by both setSocket() and + connectToHost() and can also be used on unconnected sockets. +*/ + +void Q3Socket::setSocketIntern( int socket ) +{ + if ( state() != Idle ) { + clearPendingData(); + close(); + } + Q_ULONG oldBufferSize = d ? d->readBufferSize : 0; + delete d; + + d = new Q3SocketPrivate; + if (oldBufferSize) + d->readBufferSize = oldBufferSize; + if ( socket >= 0 ) { + Q3SocketDevice *sd = new Q3SocketDevice( socket, Q3SocketDevice::Stream ); + sd->setBlocking( false ); + sd->setAddressReusable( true ); + d->setSocketDevice( this, sd ); + } + d->state = Idle; + + // Initialize the IO device flags + resetStatus(); + open( IO_ReadWrite ); + + // hm... this is not very nice. + d->host.clear(); + d->port = 0; +#ifndef QT_NO_DNS + delete d->dns4; + d->dns4 = 0; + delete d->dns6; + d->dns6 = 0; +#endif +} + + +/*! + Returns the host port number of this socket, in native byte order. +*/ + +Q_UINT16 Q3Socket::port() const +{ + if ( d->socket == 0 ) + return 0; + return d->socket->port(); +} + + +/*! + Returns the peer's host port number, normally as specified to the + connectToHost() function. If none has been set, this function + returns 0. + + Note that Qt always uses native byte order, i.e. 67 is 67 in Qt; + there is no need to call htons(). +*/ + +Q_UINT16 Q3Socket::peerPort() const +{ + if ( d->socket == 0 ) + return 0; + return d->socket->peerPort(); +} + + +/*! + Returns the host address of this socket. (This is normally the + main IP address of the host, but can be e.g. 127.0.0.1 for + connections to localhost.) +*/ + +QHostAddress Q3Socket::address() const +{ + if ( d->socket == 0 ) { + QHostAddress tmp; + return tmp; + } + return d->socket->address(); +} + + +/*! + Returns the address of the connected peer if the socket is in + Connected state; otherwise an empty QHostAddress is returned. +*/ + +QHostAddress Q3Socket::peerAddress() const +{ + if ( d->socket == 0 ) { + QHostAddress tmp; + return tmp; + } + return d->socket->peerAddress(); +} + + +/*! + Returns the host name as specified to the connectToHost() + function. An empty string is returned if none has been set. +*/ + +QString Q3Socket::peerName() const +{ + return d->host; +} + +/*! + Sets the size of the Q3Socket's internal read buffer to \a bufSize. + + Usually Q3Socket reads all data that is available from the operating + system's socket. If the buffer size is limited to a certain size, this + means that the Q3Socket class doesn't buffer more than this size of data. + + If the size of the read buffer is 0, the read buffer is unlimited and all + incoming data is buffered. This is the default. + + If you read the data in the readyRead() signal, you shouldn't use this + option since it might slow down your program unnecessary. This option is + useful if you only need to read the data at certain points in time, like in + a realtime streaming application. + + \sa readBufferSize() +*/ + +void Q3Socket::setReadBufferSize( Q_ULONG bufSize ) +{ + d->readBufferSize = bufSize; +} + +/*! + Returns the size of the read buffer. + + \sa setReadBufferSize() +*/ + +Q_ULONG Q3Socket::readBufferSize() const +{ + return d->readBufferSize; +} + +/*! + \fn bool Q3Socket::isSequential() const + \internal +*/ + +QT_END_NAMESPACE + +#endif //QT_NO_NETWORK diff --git a/src/qt3support/network/q3socket.h b/src/qt3support/network/q3socket.h new file mode 100644 index 0000000..7922406 --- /dev/null +++ b/src/qt3support/network/q3socket.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3SOCKET_H +#define Q3SOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtNetwork/qhostaddress.h> // int->QHostAddress conversion + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +class Q3SocketPrivate; +class Q3SocketDevice; + +class Q_COMPAT_EXPORT Q3Socket : public QIODevice +{ + Q_OBJECT +public: + enum Error { + ErrConnectionRefused, + ErrHostNotFound, + ErrSocketRead + }; + + Q3Socket( QObject *parent=0, const char *name=0 ); + virtual ~Q3Socket(); + + enum State { Idle, HostLookup, Connecting, + Connected, Closing, + Connection=Connected }; + State state() const; + + int socket() const; + virtual void setSocket( int ); + + Q3SocketDevice *socketDevice(); + virtual void setSocketDevice( Q3SocketDevice * ); + +#ifndef QT_NO_DNS + virtual void connectToHost( const QString &host, Q_UINT16 port ); +#endif + QString peerName() const; + + // Implementation of QIODevice abstract virtual functions + bool open( OpenMode mode ); + bool open(int mode) { return open((OpenMode)mode); } + void close(); + bool flush(); + Offset size() const; + Offset at() const; + bool at( Offset ); + bool atEnd() const; + + qint64 bytesAvailable() const; + Q_ULONG waitForMore( int msecs, bool *timeout ) const; + Q_ULONG waitForMore( int msecs ) const; // ### Qt 4.0: merge the two overloads + qint64 bytesToWrite() const; + void clearPendingData(); + + int getch(); + int putch( int ); + int ungetch(int); + + bool canReadLine() const; + + Q_UINT16 port() const; + Q_UINT16 peerPort() const; + QHostAddress address() const; + QHostAddress peerAddress() const; + + void setReadBufferSize( Q_ULONG ); + Q_ULONG readBufferSize() const; + + inline bool isSequential() const { return true; } + +Q_SIGNALS: + void hostFound(); + void connected(); + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten( int nbytes ); + void error( int ); + +protected Q_SLOTS: + virtual void sn_read( bool force=false ); + virtual void sn_write(); + +protected: + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + +private Q_SLOTS: + void tryConnecting(); + void emitErrorConnectionRefused(); + +private: + Q3SocketPrivate *d; + + bool consumeWriteBuf( Q_ULONG nbytes ); + void tryConnection(); + void setSocketIntern( int socket ); + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + Q3Socket( const Q3Socket & ); + Q3Socket &operator=( const Q3Socket & ); +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3SOCKET_H diff --git a/src/qt3support/network/q3socketdevice.cpp b/src/qt3support/network/q3socketdevice.cpp new file mode 100644 index 0000000..7e53163 --- /dev/null +++ b/src/qt3support/network/q3socketdevice.cpp @@ -0,0 +1,757 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3socketdevice.h" +#ifndef QT_NO_NETWORK + +#include "qwindowdefs.h" +#include <string.h> + +QT_BEGIN_NAMESPACE + +//#define Q3SOCKETDEVICE_DEBUG + + +class Q3SocketDevicePrivate +{ +public: + Q3SocketDevicePrivate( Q3SocketDevice::Protocol p ) + : protocol(p) + { } + + Q3SocketDevice::Protocol protocol; +}; + + +/*! + \class Q3SocketDevice + \brief The Q3SocketDevice class provides a platform-independent low-level socket API. + + \compat + \reentrant + + This class provides a low level API for working with sockets. Users of + this class are assumed to have networking experience. For most users the + Q3Socket class provides a much easier and high level alternative, but + certain things (like UDP) can't be done with Q3Socket and if you need a + platform-independent API for those, Q3SocketDevice is the right choice. + + The essential purpose of the class is to provide a QIODevice that + works on sockets, wrapped in a platform-independent API. + + When calling connect() or bind(), Q3SocketDevice detects the + protocol family (IPv4, IPv6) automatically. Passing the protocol + family to Q3SocketDevice's constructor or to setSocket() forces + creation of a socket device of a specific protocol. If not set, the + protocol will be detected at the first call to connect() or bind(). + + \sa Q3Socket, QSocketNotifier, QHostAddress +*/ + + +/*! + \enum Q3SocketDevice::Protocol + + This enum type describes the protocol family of the socket. Possible values + are: + + \value IPv4 The socket is an IPv4 socket. + \value IPv6 The socket is an IPv6 socket. + \value Unknown The protocol family of the socket is not known. This can + happen if you use Q3SocketDevice with an already existing socket; it + tries to determine the protocol family, but this can fail if the + protocol family is not known to Q3SocketDevice. + + \sa protocol() setSocket() +*/ + +/*! + \enum Q3SocketDevice::Error + + This enum type describes the error states of Q3SocketDevice. + + \value NoError No error has occurred. + + \value AlreadyBound The device is already bound, according to bind(). + + \value Inaccessible The operating system or firewall prohibited + the action. + + \value NoResources The operating system ran out of a resource. + + \value InternalError An internal error occurred in Q3SocketDevice. + + \value Impossible An attempt was made to do something which makes + no sense. For example: + \snippet doc/src/snippets/code/src_qt3support_network_q3socketdevice.cpp 0 + The libc ::close() closes the socket, but Q3SocketDevice is not aware + of this. So when you call writeBlock(), the impossible happens. + + \value NoFiles The operating system will not let Q3SocketDevice open + another file. + + \value ConnectionRefused A connection attempt was rejected by the + peer. + + \value NetworkFailure There is a network failure. + + \value UnknownError The operating system did something + unexpected. + + \omitvalue Bug +*/ + +/*! + \enum Q3SocketDevice::Type + + This enum type describes the type of the socket: + \value Stream a stream socket (TCP, usually) + \value Datagram a datagram socket (UDP, usually) +*/ + + +/*! + Creates a Q3SocketDevice object for the existing socket \a socket. + + The \a type argument must match the actual socket type; use \c + Q3SocketDevice::Stream for a reliable, connection-oriented TCP + socket, or Q3SocketDevice::Datagram for an unreliable, + connectionless UDP socket. +*/ +Q3SocketDevice::Q3SocketDevice( int socket, Type type ) + : fd( socket ), t( type ), p( 0 ), pp( 0 ), e( NoError ), + d(new Q3SocketDevicePrivate(Unknown)) +{ +#if defined(Q3SOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice: Created Q3SocketDevice %p (socket %x, type %d)", + this, socket, type ); +#endif + init(); + setSocket( socket, type ); +} + +/*! + Creates a Q3SocketDevice object for a stream or datagram socket. + + The \a type argument must be either Q3SocketDevice::Stream for a + reliable, connection-oriented TCP socket, or \c + Q3SocketDevice::Datagram for an unreliable UDP socket. + + The socket is created as an IPv4 socket. + + \sa blocking() protocol() +*/ +Q3SocketDevice::Q3SocketDevice( Type type ) + : fd( -1 ), t( type ), p( 0 ), pp( 0 ), e( NoError ), + d(new Q3SocketDevicePrivate(IPv4)) +{ +#if defined(Q3SOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice: Created Q3SocketDevice object %p, type %d", + this, type ); +#endif + init(); + setSocket( createNewSocket(), type ); +} + +/*! + Creates a Q3SocketDevice object for a stream or datagram socket. + + The \a type argument must be either Q3SocketDevice::Stream for a + reliable, connection-oriented TCP socket, or \c + Q3SocketDevice::Datagram for an unreliable UDP socket. + + The \a protocol indicates whether the socket should be of type IPv4 + or IPv6. Passing \c Unknown is not meaningful in this context and you + should avoid using (it creates an IPv4 socket, but your code is not easily + readable). + + The argument \a dummy is necessary for compatibility with some + compilers. + + \sa blocking() protocol() +*/ +Q3SocketDevice::Q3SocketDevice( Type type, Protocol protocol, int ) + : fd( -1 ), t( type ), p( 0 ), pp( 0 ), e( NoError ), + d(new Q3SocketDevicePrivate(protocol)) +{ +#if defined(Q3SOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice: Created Q3SocketDevice object %p, type %d", + this, type ); +#endif + init(); + setSocket( createNewSocket(), type ); +} + +/*! + Destroys the socket device and closes the socket if it is open. +*/ +Q3SocketDevice::~Q3SocketDevice() +{ + close(); + delete d; + d = 0; +#if defined(Q3SOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice: Destroyed Q3SocketDevice %p", this ); +#endif +} + + +/*! + Returns true if this is a valid socket; otherwise returns false. + + \sa socket() +*/ +bool Q3SocketDevice::isValid() const +{ + return fd != -1; +} + + +/*! + \fn Type Q3SocketDevice::type() const + + Returns the socket type which is either Q3SocketDevice::Stream + or Q3SocketDevice::Datagram. + + \sa socket() +*/ +Q3SocketDevice::Type Q3SocketDevice::type() const +{ + return t; +} + +/*! + Returns the socket's protocol family, which is one of \c Unknown, \c IPv4, + or \c IPv6. + + Q3SocketDevice either creates a socket with a well known protocol family or + it uses an already existing socket. In the first case, this function + returns the protocol family it was constructed with. In the second case, it + tries to determine the protocol family of the socket; if this fails, it + returns \c Unknown. + + \sa Protocol setSocket() +*/ +Q3SocketDevice::Protocol Q3SocketDevice::protocol() const +{ + if ( d->protocol == Unknown ) + d->protocol = getProtocol(); + return d->protocol; +} + +/*! + Returns the socket number, or -1 if it is an invalid socket. + + \sa isValid(), type() +*/ +int Q3SocketDevice::socket() const +{ + return fd; +} + + +/*! + Sets the socket device to operate on the existing socket \a + socket. + + The \a type argument must match the actual socket type; use \c + Q3SocketDevice::Stream for a reliable, connection-oriented TCP + socket, or Q3SocketDevice::Datagram for an unreliable, + connectionless UDP socket. + + Any existing socket is closed. + + \sa isValid(), close() +*/ +void Q3SocketDevice::setSocket( int socket, Type type ) +{ + if ( fd != -1 ) // close any open socket + close(); +#if defined(Q3SOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice::setSocket: socket %x, type %d", socket, type ); +#endif + t = type; + fd = socket; + d->protocol = Unknown; + e = NoError; + resetStatus(); + open( ReadWrite ); + fetchConnectionParameters(); +} + + +/*! + Opens the socket using the specified QIODevice file \a mode. This + function is called from the Q3SocketDevice constructors and from + the setSocket() function. You should not call it yourself. + + \sa close() +*/ +bool Q3SocketDevice::open( OpenMode mode ) +{ + if ( isOpen() || !isValid() ) + return false; +#if defined(Q3SOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice::open: mode %x", mode ); +#endif + setOpenMode( (mode & ReadWrite) | Unbuffered ); + return true; +} + +/*! + \fn bool Q3SocketDevice::open(int mode) + \overload +*/ +/*! + The current Q3SocketDevice implementation does not buffer at all, + so this is a no-op. This function always returns true. +*/ +bool Q3SocketDevice::flush() +{ + return true; +} + + +/*! + \reimp + + The size is meaningless for a socket, therefore this function returns 0. +*/ +QIODevice::Offset Q3SocketDevice::size() const +{ + return 0; +} + + +/*! + The read/write index is meaningless for a socket, therefore this + function returns 0. +*/ +QIODevice::Offset Q3SocketDevice::at() const +{ + return 0; +} + + +/*! + The read/write index is meaningless for a socket, therefore this + function does nothing and returns true. + + The \a offset parameter is ignored. +*/ +bool Q3SocketDevice::at( Offset /* offset */ ) +{ + return true; +} + + +/*! + \reimp + + Returns true if no data is currently available at the socket; + otherwise returns false. +*/ +bool Q3SocketDevice::atEnd() const +{ + return bytesAvailable() <= 0; +} + +/*! + Returns true if the address of this socket can be used by other + sockets at the same time, and false if this socket claims + exclusive ownership. + + \sa setAddressReusable() +*/ +bool Q3SocketDevice::addressReusable() const +{ + return option( ReuseAddress ); +} + + +/*! + Sets the address of this socket to be usable by other sockets too + if \a enable is true, and to be used exclusively by this socket if + \a enable is false. + + When a socket is reusable, other sockets can use the same port + number (and IP address), which is generally useful. Of course + other sockets cannot use the same + (address,port,peer-address,peer-port) 4-tuple as this socket, so + there is no risk of confusing the two TCP connections. + + \sa addressReusable() +*/ +void Q3SocketDevice::setAddressReusable( bool enable ) +{ + setOption( ReuseAddress, enable ); +} + + +/*! + Returns the size of the operating system receive buffer. + + \sa setReceiveBufferSize() +*/ +int Q3SocketDevice::receiveBufferSize() const +{ + return option( ReceiveBuffer ); +} + + +/*! + Sets the size of the operating system receive buffer to \a size. + + The operating system receive buffer size effectively limits two + things: how much data can be in transit at any one moment, and how + much data can be received in one iteration of the main event loop. + + The default is operating system-dependent. A socket that receives + large amounts of data is probably best with a buffer size of + 49152. +*/ +void Q3SocketDevice::setReceiveBufferSize( uint size ) +{ + setOption( ReceiveBuffer, size ); +} + + +/*! + Returns the size of the operating system send buffer. + + \sa setSendBufferSize() +*/ +int Q3SocketDevice::sendBufferSize() const +{ + return option( SendBuffer ); +} + + +/*! + Sets the size of the operating system send buffer to \a size. + + The operating system send buffer size effectively limits how much + data can be in transit at any one moment. + + The default is operating system-dependent. A socket that sends + large amounts of data is probably best with a buffer size of + 49152. +*/ +void Q3SocketDevice::setSendBufferSize( uint size ) +{ + setOption( SendBuffer, size ); +} + + +/*! + Returns the port number of this socket device. This may be 0 for a + while, but is set to something sensible as soon as a sensible + value is available. + + Note that Qt always uses native byte order, i.e. 67 is 67 in Qt; + there is no need to call htons(). +*/ +quint16 Q3SocketDevice::port() const +{ + return p; +} + + +/*! + Returns the address of this socket device. This may be 0.0.0.0 for + a while, but is set to something sensible as soon as a sensible + value is available. +*/ +QHostAddress Q3SocketDevice::address() const +{ + return a; +} + + +/*! + Returns the first error seen. +*/ +Q3SocketDevice::Error Q3SocketDevice::error() const +{ + return e; +} + + +/*! + Allows subclasses to set the error state to \a err. +*/ +void Q3SocketDevice::setError( Error err ) +{ + e = err; +} + +/*! \fn Q3SocketDevice::readBlock(char *data, Q_ULONG maxlen) + + Reads \a maxlen bytes from the socket into \a data and returns the + number of bytes read. Returns -1 if an error occurred. Returning 0 + is not an error. For Stream sockets, 0 is returned when the remote + host closes the connection. For Datagram sockets, 0 is a valid + datagram size. +*/ + +/*! \fn Q3SocketDevice::writeBlock(const char *data, Q_ULONG len) + + Writes \a len bytes to the socket from \a data and returns the + number of bytes written. Returns -1 if an error occurred. + + This is used for Q3SocketDevice::Stream sockets. +*/ + +/*! + \fn Q_LONG Q3SocketDevice::writeBlock( const char * data, Q_ULONG len, + const QHostAddress & host, Q_UINT16 port ) + \overload + + Writes \a len bytes to the socket from \a data and returns the + number of bytes written. Returns -1 if an error occurred. + + This is used for Q3SocketDevice::Datagram sockets. You must + specify the \a host and \a port of the destination of the data. +*/ + +/*! + \fn bool Q3SocketDevice::isSequential() const + \internal +*/ + +/*! + \fn qint64 Q3SocketDevice::readData( char *data, qint64 maxlen ) + + Reads \a maxlen bytes from the socket into \a data and returns the + number of bytes read. Returns -1 if an error occurred. +*/ + +/*! + \fn int Q3SocketDevice::createNewSocket() + + Creates a new socket identifier. Returns -1 if there is a failure + to create the new identifier; error() explains why. + + \sa setSocket() +*/ + +/*! + \fn void Q3SocketDevice::close() + \reimp + + Closes the socket and sets the socket identifier to -1 (invalid). + + (This function ignores errors; if there are any then a file + descriptor leakage might result. As far as we know, the only error + that can arise is EBADF, and that would of course not cause + leakage. There may be OS-specific errors that we haven't come + across, however.) + + \sa open() +*/ + +/*! + \fn bool Q3SocketDevice::blocking() const + + Returns true if the socket is valid and in blocking mode; + otherwise returns false. + + Note that this function does not set error(). + + \warning On Windows, this function always returns true since the + ioctlsocket() function is broken. + + \sa setBlocking(), isValid() +*/ + +/*! + \fn void Q3SocketDevice::setBlocking( bool enable ) + + Makes the socket blocking if \a enable is true or nonblocking if + \a enable is false. + + Sockets are blocking by default, but we recommend using + nonblocking socket operations, especially for GUI programs that + need to be responsive. + + \warning On Windows, this function should be used with care since + whenever you use a QSocketNotifier on Windows, the socket is + immediately made nonblocking. + + \sa blocking(), isValid() +*/ + +/*! + \fn int Q3SocketDevice::option( Option opt ) const + + Returns the value of the socket option \a opt. +*/ + +/*! + \fn void Q3SocketDevice::setOption( Option opt, int v ) + + Sets the socket option \a opt to \a v. +*/ + +/*! + \fn bool Q3SocketDevice::connect( const QHostAddress &addr, Q_UINT16 port ) + + Connects to the IP address and port specified by \a addr and \a + port. Returns true if it establishes a connection; otherwise returns false. + If it returns false, error() explains why. + + Note that error() commonly returns NoError for non-blocking + sockets; this just means that you can call connect() again in a + little while and it'll probably succeed. +*/ + +/*! + \fn bool Q3SocketDevice::bind( const QHostAddress &address, Q_UINT16 port ) + + Assigns a name to an unnamed socket. The name is the host address + \a address and the port number \a port. If the operation succeeds, + bind() returns true; otherwise it returns false without changing + what port() and address() return. + + bind() is used by servers for setting up incoming connections. + Call bind() before listen(). +*/ + +/*! + \fn bool Q3SocketDevice::listen( int backlog ) + + Specifies how many pending connections a server socket can have. + Returns true if the operation was successful; otherwise returns + false. A \a backlog value of 50 is quite common. + + The listen() call only applies to sockets where type() is \c + Stream, i.e. not to \c Datagram sockets. listen() must not be + called before bind() or after accept(). + + \sa bind(), accept() +*/ + +/*! + \fn int Q3SocketDevice::accept() + + Extracts the first connection from the queue of pending + connections for this socket and returns a new socket identifier. + Returns -1 if the operation failed. + + \sa bind(), listen() +*/ + +/*! + \fn qint64 Q3SocketDevice::bytesAvailable() const + + Returns the number of bytes available for reading, or -1 if an + error occurred. + + \warning On Microsoft Windows, we use the ioctlsocket() function + to determine the number of bytes queued on the socket. According + to Microsoft (KB Q125486), ioctlsocket() sometimes returns an + incorrect number. The only safe way to determine the amount of + data on the socket is to read it using readBlock(). QSocket has + workarounds to deal with this problem. +*/ + +/*! + \fn Q_LONG Q3SocketDevice::waitForMore( int msecs, bool *timeout ) const + + Wait up to \a msecs milliseconds for more data to be available. If + \a msecs is -1 the call will block indefinitely. + + Returns the number of bytes available for reading, or -1 if an + error occurred. + + If \a timeout is non-null and no error occurred (i.e. it does not + return -1): this function sets *\a timeout to true, if the reason + for returning was that the timeout was reached; otherwise it sets + *\a timeout to false. This is useful to find out if the peer + closed the connection. + + \warning This is a blocking call and should be avoided in event + driven applications. + + \sa bytesAvailable() +*/ + +/*! + \fn qint64 Q3SocketDevice::writeData( const char *data, qint64 len ) + + Writes \a len bytes to the socket from \a data and returns the + number of bytes written. Returns -1 if an error occurred. + + This is used for Q3SocketDevice::Stream sockets. +*/ + +/*! + \fn void Q3SocketDevice::fetchConnectionParameters() + + Fetches information about both ends of the connection: whatever is + available. +*/ + +/*! + \fn Q_UINT16 Q3SocketDevice::peerPort() const + + Returns the port number of the port this socket device is + connected to. This may be 0 for a while, but is set to something + sensible as soon as a sensible value is available. + + Note that for Datagram sockets, this is the source port of the + last packet received, and that it is in native byte order. +*/ + +/*! + \fn QHostAddress Q3SocketDevice::peerAddress() const + + Returns the address of the port this socket device is connected + to. This may be 0.0.0.0 for a while, but is set to something + sensible as soon as a sensible value is available. + + Note that for Datagram sockets, this is the source port of the + last packet received. +*/ + +QT_END_NAMESPACE + +#endif //QT_NO_NETWORK diff --git a/src/qt3support/network/q3socketdevice.h b/src/qt3support/network/q3socketdevice.h new file mode 100644 index 0000000..fde591d --- /dev/null +++ b/src/qt3support/network/q3socketdevice.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3SOCKETDEVICE_H +#define Q3SOCKETDEVICE_H + +#include <QtCore/qiodevice.h> +#include <QtNetwork/qhostaddress.h> // int->QHostAddress conversion + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3Support) + +#ifndef QT_NO_NETWORK + +class Q3SocketDevicePrivate; + +class Q_COMPAT_EXPORT Q3SocketDevice: public QIODevice +{ +public: + enum Type { Stream, Datagram }; + enum Protocol { IPv4, IPv6, Unknown }; + + Q3SocketDevice( Type type = Stream ); + Q3SocketDevice( Type type, Protocol protocol, int dummy ); + Q3SocketDevice( int socket, Type type ); + virtual ~Q3SocketDevice(); + + bool isValid() const; + Type type() const; + Protocol protocol() const; + + int socket() const; + virtual void setSocket( int socket, Type type ); + + bool open( OpenMode mode ); + bool open( int mode ) { return open((OpenMode)mode); } + void close(); + bool flush(); + + // Implementation of QIODevice abstract virtual functions + Offset size() const; + Offset at() const; + bool at( Offset ); + bool atEnd() const; + + bool blocking() const; + virtual void setBlocking( bool ); + + bool addressReusable() const; + virtual void setAddressReusable( bool ); + + int receiveBufferSize() const; + virtual void setReceiveBufferSize( uint ); + int sendBufferSize() const; + virtual void setSendBufferSize( uint ); + + virtual bool connect( const QHostAddress &, Q_UINT16 ); + + virtual bool bind( const QHostAddress &, Q_UINT16 ); + virtual bool listen( int backlog ); + virtual int accept(); + + qint64 bytesAvailable() const; + Q_LONG waitForMore( int msecs, bool *timeout=0 ) const; + virtual Q_LONG writeBlock( const char *data, Q_ULONG len, + const QHostAddress & host, Q_UINT16 port ); + inline Q_LONG writeBlock(const char *data, Q_ULONG len) + { return qint64(write(data, qint64(len))); } + inline qint64 readBlock(char *data, Q_ULONG maxlen) + { return qint64(read(data, qint64(maxlen))); } + + Q_UINT16 port() const; + Q_UINT16 peerPort() const; + QHostAddress address() const; + QHostAddress peerAddress() const; + + enum Error { + NoError, + AlreadyBound, + Inaccessible, + NoResources, + InternalError, + Bug = InternalError, // ### remove in 4.0? + Impossible, + NoFiles, + ConnectionRefused, + NetworkFailure, + UnknownError + }; + Error error() const; + + inline bool isSequential() const { return true; } + +protected: + void setError( Error err ); + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + +private: + int fd; + Type t; + Q_UINT16 p; + QHostAddress a; + Q_UINT16 pp; + QHostAddress pa; + Q3SocketDevice::Error e; + Q3SocketDevicePrivate * d; + + enum Option { Broadcast, ReceiveBuffer, ReuseAddress, SendBuffer }; + + int option( Option ) const; + virtual void setOption( Option, int ); + + void fetchConnectionParameters(); +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) + void fetchPeerConnectionParameters(); +#endif + + static void init(); + int createNewSocket(); + Protocol getProtocol() const; + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + Q3SocketDevice( const Q3SocketDevice & ); + Q3SocketDevice &operator=( const Q3SocketDevice & ); +#endif +}; + +#endif // QT_NO_NETWORK + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3SOCKETDEVICE_H diff --git a/src/qt3support/network/q3socketdevice_unix.cpp b/src/qt3support/network/q3socketdevice_unix.cpp new file mode 100644 index 0000000..e83a789 --- /dev/null +++ b/src/qt3support/network/q3socketdevice_unix.cpp @@ -0,0 +1,926 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +// Almost always the same. If not, specify in qplatformdefs.h. +#if !defined(QT_SOCKOPTLEN_T) +# define QT_SOCKOPTLEN_T QT_SOCKLEN_T +#endif + +// Tru64 redefines accept -> _accept with _XOPEN_SOURCE_EXTENDED +static inline int qt_socket_accept(int s, struct sockaddr *addr, QT_SOCKLEN_T *addrlen) +{ return ::accept(s, addr, addrlen); } +#if defined(accept) +# undef accept +#endif + +// UnixWare 7 redefines listen -> _listen +static inline int qt_socket_listen(int s, int backlog) +{ return ::listen(s, backlog); } +#if defined(listen) +# undef listen +#endif + +// UnixWare 7 redefines socket -> _socket +static inline int qt_socket_socket(int domain, int type, int protocol) +{ return ::socket(domain, type, protocol); } +#if defined(socket) +# undef socket +#endif + +#include "q3socketdevice.h" + +#ifndef QT_NO_NETWORK + +#include "qwindowdefs.h" + +#include <errno.h> +#include <sys/types.h> + +QT_BEGIN_NAMESPACE + +static inline void qt_socket_getportaddr( struct sockaddr *sa, + Q_UINT16 *port, QHostAddress *addr ) +{ +#if !defined(QT_NO_IPV6) + if ( sa->sa_family == AF_INET6 ) { + struct sockaddr_in6 *sa6 = ( struct sockaddr_in6 * )sa; + Q_IPV6ADDR tmp; + memcpy( &tmp, &sa6->sin6_addr.s6_addr, sizeof(tmp) ); + QHostAddress a( tmp ); + *addr = a; + *port = ntohs( sa6->sin6_port ); + return; + } +#endif + struct sockaddr_in *sa4 = (struct sockaddr_in *)sa; + QHostAddress a( ntohl( sa4->sin_addr.s_addr ) ); + *port = ntohs( sa4->sin_port ); + *addr = QHostAddress( ntohl( sa4->sin_addr.s_addr ) ); + return; +} + + +//#define QSOCKETDEVICE_DEBUG + +// internal +void Q3SocketDevice::init() +{ +} + + +Q3SocketDevice::Protocol Q3SocketDevice::getProtocol() const +{ + if ( isValid() ) { +#if !defined (QT_NO_IPV6) + struct sockaddr_storage sa; +#else + struct sockaddr sa; +#endif + memset( &sa, 0, sizeof(sa) ); + QT_SOCKLEN_T sz = sizeof( sa ); +#if !defined (QT_NO_IPV6) + struct sockaddr *sap = reinterpret_cast<struct sockaddr *>(&sa); + if ( !::getsockname(fd, sap, &sz) ) { + switch ( sap->sa_family ) { + case AF_INET: + return IPv4; + case AF_INET6: + return IPv6; + default: + return Unknown; + } + } +#else + if ( !::getsockname(fd, &sa, &sz) ) { + switch ( sa.sa_family ) { + case AF_INET: + return IPv4; + default: + return Unknown; + } + } +#endif + } + return Unknown; +} + + +int Q3SocketDevice::createNewSocket() +{ +#if !defined(QT_NO_IPV6) + int s = qt_socket_socket( protocol() == IPv6 ? AF_INET6 : AF_INET, + t == Datagram ? SOCK_DGRAM : SOCK_STREAM, 0 ); +#else + int s = qt_socket_socket( AF_INET, t==Datagram?SOCK_DGRAM:SOCK_STREAM, 0 ); +#endif + if ( s < 0 ) { + switch( errno ) { + case EPROTONOSUPPORT: + e = InternalError; // 0 is supposed to work for both types + break; + case ENFILE: + e = NoFiles; // special case for this + break; + case EACCES: + e = Inaccessible; + break; + case ENOBUFS: + case ENOMEM: + e = NoResources; + break; + case EINVAL: + e = Impossible; + break; + default: + e = UnknownError; + break; + } + } else { + return s; + } + return -1; +} + +void Q3SocketDevice::close() +{ + if ( fd == -1 || !isOpen() ) // already closed + return; + resetStatus(); + setOpenMode(NotOpen); + ::close( fd ); +#if defined(QSOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice::close: Closed socket %x", fd ); +#endif + fd = -1; + fetchConnectionParameters(); + QIODevice::close(); +} + + +bool Q3SocketDevice::blocking() const +{ + if ( !isValid() ) + return true; + int s = fcntl(fd, F_GETFL, 0); + return !(s >= 0 && ((s & O_NDELAY) != 0)); +} + + +void Q3SocketDevice::setBlocking( bool enable ) +{ +#if defined(QSOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice::setBlocking( %d )", enable ); +#endif + if ( !isValid() ) + return; + int tmp = ::fcntl(fd, F_GETFL, 0); + if ( tmp >= 0 ) + tmp = ::fcntl( fd, F_SETFL, enable ? (tmp&~O_NDELAY) : (tmp|O_NDELAY) ); + if ( tmp >= 0 ) + return; + if ( e ) + return; + switch( errno ) { + case EACCES: + case EBADF: + e = Impossible; + break; + case EFAULT: + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + case EDEADLK: + case EINTR: + case EINVAL: + case EMFILE: + case ENOLCK: + case EPERM: + default: + e = UnknownError; + } +} + + +int Q3SocketDevice::option( Option opt ) const +{ + if ( !isValid() ) + return -1; + int n = -1; + int v = -1; + switch ( opt ) { + case Broadcast: + n = SO_BROADCAST; + break; + case ReceiveBuffer: + n = SO_RCVBUF; + break; + case ReuseAddress: + n = SO_REUSEADDR; + break; + case SendBuffer: + n = SO_SNDBUF; + break; + } + if ( n != -1 ) { + QT_SOCKOPTLEN_T len; + len = sizeof(v); + int r = ::getsockopt( fd, SOL_SOCKET, n, (char*)&v, &len ); + if ( r >= 0 ) + return v; + if ( !e ) { + Q3SocketDevice *that = (Q3SocketDevice*)this; // mutable function + switch( errno ) { + case EBADF: + case ENOTSOCK: + that->e = Impossible; + break; + case EFAULT: + that->e = InternalError; + break; + default: + that->e = UnknownError; + break; + } + } + return -1; + } + return v; +} + + +void Q3SocketDevice::setOption( Option opt, int v ) +{ + if ( !isValid() ) + return; + int n = -1; // for really, really bad compilers + switch ( opt ) { + case Broadcast: + n = SO_BROADCAST; + break; + case ReceiveBuffer: + n = SO_RCVBUF; + break; + case ReuseAddress: + n = SO_REUSEADDR; + break; + case SendBuffer: + n = SO_SNDBUF; + break; + default: + return; + } + if ( ::setsockopt( fd, SOL_SOCKET, n, (char*)&v, sizeof(v)) < 0 && + e == NoError ) { + switch( errno ) { + case EBADF: + case ENOTSOCK: + e = Impossible; + break; + case EFAULT: + e = InternalError; + break; + default: + e = UnknownError; + break; + } + } +} + + +bool Q3SocketDevice::connect( const QHostAddress &addr, Q_UINT16 port ) +{ + if ( !isValid() ) + return false; + + pa = addr; + pp = port; + + struct sockaddr_in a4; + struct sockaddr *aa; + QT_SOCKLEN_T aalen; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 a6; + + if ( addr.isIPv6Address() ) { + memset( &a6, 0, sizeof(a6) ); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons( port ); + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + memcpy( &a6.sin6_addr.s6_addr, &ip6, sizeof(ip6) ); + + aalen = sizeof( a6 ); + aa = (struct sockaddr *)&a6; + } else +#endif + if ( addr.isIPv4Address() ) { + memset( &a4, 0, sizeof(a4) ); + a4.sin_family = AF_INET; + a4.sin_port = htons( port ); + a4.sin_addr.s_addr = htonl( addr.toIPv4Address() ); + + aalen = sizeof(a4); + aa = (struct sockaddr *)&a4; + } else { + e = Impossible; + return false; + } + + int r = QT_SOCKET_CONNECT( fd, aa, aalen ); + if ( r == 0 ) { + fetchConnectionParameters(); + return true; + } + if ( errno == EISCONN || errno == EALREADY || errno == EINPROGRESS ) { + fetchConnectionParameters(); + return true; + } + if ( e != NoError || errno == EAGAIN || errno == EWOULDBLOCK ) { + return false; + } + switch( errno ) { + case EBADF: + case ENOTSOCK: + e = Impossible; + break; + case EFAULT: + case EAFNOSUPPORT: + e = InternalError; + break; + case ECONNREFUSED: + e = ConnectionRefused; + break; + case ETIMEDOUT: + case ENETUNREACH: + e = NetworkFailure; + break; + case EADDRINUSE: + e = NoResources; + break; + case EACCES: + case EPERM: + e = Inaccessible; + break; + default: + e = UnknownError; + break; + } + return false; +} + + +bool Q3SocketDevice::bind( const QHostAddress &address, Q_UINT16 port ) +{ + if ( !isValid() ) + return false; + int r; + struct sockaddr_in a4; +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 a6; + + if ( address.isIPv6Address() ) { + memset( &a6, 0, sizeof(a6) ); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons( port ); + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy( &a6.sin6_addr.s6_addr, &tmp, sizeof(tmp) ); + + r = QT_SOCKET_BIND( fd, (struct sockaddr *)&a6, sizeof(a6) ); + } else +#endif + if ( address.isIPv4Address() ) { + memset( &a4, 0, sizeof(a4) ); + a4.sin_family = AF_INET; + a4.sin_port = htons( port ); + a4.sin_addr.s_addr = htonl( address.toIPv4Address() ); + + r = QT_SOCKET_BIND( fd, (struct sockaddr*)&a4, sizeof(a4) ); + } else { + e = Impossible; + return false; + } + + if ( r < 0 ) { + switch( errno ) { + case EINVAL: + e = AlreadyBound; + break; + case EACCES: + e = Inaccessible; + break; + case ENOMEM: + e = NoResources; + break; + case EFAULT: // a was illegal + case ENAMETOOLONG: // sz was wrong + e = InternalError; + break; + case EBADF: // AF_UNIX only + case ENOTSOCK: // AF_UNIX only + case EROFS: // AF_UNIX only + case ENOENT: // AF_UNIX only + case ENOTDIR: // AF_UNIX only + case ELOOP: // AF_UNIX only + e = Impossible; + break; + default: + e = UnknownError; + break; + } + return false; + } + fetchConnectionParameters(); + return true; +} + + +bool Q3SocketDevice::listen( int backlog ) +{ + if ( !isValid() ) + return false; + if ( qt_socket_listen( fd, backlog ) >= 0 ) + return true; + if ( !e ) + e = Impossible; + return false; +} + + +int Q3SocketDevice::accept() +{ + if ( !isValid() ) + return -1; + +#if !defined (QT_NO_IPV6) + struct sockaddr_storage aa; +#else + struct sockaddr aa; +#endif + QT_SOCKLEN_T l = sizeof( aa ); + bool done; + int s; + do { + s = qt_socket_accept( fd, (struct sockaddr*)&aa, &l ); + // we'll blithely throw away the stuff accept() wrote to aa + done = true; + if ( s < 0 && e == NoError ) { + switch( errno ) { + case EINTR: + done = false; + break; +#if defined(EPROTO) + case EPROTO: +#endif +#if defined(ENONET) + case ENONET: +#endif + case ENOPROTOOPT: + case EHOSTDOWN: + case EOPNOTSUPP: + case EHOSTUNREACH: + case ENETDOWN: + case ENETUNREACH: + case ETIMEDOUT: + // in all these cases, an error happened during connection + // setup. we're not interested in what happened, so we + // just treat it like the client-closed-quickly case. + case EPERM: + // firewalling wouldn't let us accept. we treat it like + // the client-closed-quickly case. + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + // the client closed the connection before we got around + // to accept()ing it. + break; + case EBADF: + case ENOTSOCK: + e = Impossible; + break; + case EFAULT: + e = InternalError; + break; + case ENOMEM: + case ENOBUFS: + e = NoResources; + break; + default: + e = UnknownError; + break; + } + } + } while (!done); + return s; +} + + +qint64 Q3SocketDevice::bytesAvailable() const +{ + if ( !isValid() ) + return -1; + + /* + Apparently, there is not consistency among different operating + systems on how to use FIONREAD. + + FreeBSD, Linux and Solaris all expect the 3rd argument to + ioctl() to be an int, which is normally 32-bit even on 64-bit + machines. + + IRIX, on the other hand, expects a size_t, which is 64-bit on + 64-bit machines. + + So, the solution is to use size_t initialized to zero to make + sure all bits are set to zero, preventing underflow with the + FreeBSD/Linux/Solaris ioctls. + */ + size_t nbytes = 0; + // gives shorter than true amounts on Unix domain sockets. + if ( ::ioctl(fd, FIONREAD, (char*)&nbytes) < 0 ) + return -1; + return (Q_LONG) *((int *) &nbytes) + QIODevice::bytesAvailable(); +} + + +Q_LONG Q3SocketDevice::waitForMore( int msecs, bool *timeout ) const +{ + if ( !isValid() ) + return -1; + if ( fd >= FD_SETSIZE ) + return -1; + + fd_set fds; + struct timeval tv; + + FD_ZERO( &fds ); + FD_SET( fd, &fds ); + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; + + int rv = select( fd+1, &fds, 0, 0, msecs < 0 ? 0 : &tv ); + + if ( rv < 0 ) + return -1; + + if ( timeout ) { + if ( rv == 0 ) + *timeout = true; + else + *timeout = false; + } + + return bytesAvailable(); +} + + +qint64 Q3SocketDevice::readData( char *data, qint64 maxlen ) +{ +#if defined(QT_CHECK_NULL) + if ( data == 0 && maxlen != 0 ) { + qWarning( "Q3SocketDevice::readBlock: Null pointer error" ); + } +#endif +#if defined(QT_CHECK_STATE) + if ( !isValid() ) { + qWarning( "Q3SocketDevice::readBlock: Invalid socket" ); + return -1; + } + if ( !isOpen() ) { + qWarning( "Q3SocketDevice::readBlock: Device is not open" ); + return -1; + } + if ( !isReadable() ) { + qWarning( "Q3SocketDevice::readBlock: Read operation not permitted" ); + return -1; + } +#endif + bool done = false; + int r = 0; + while ( done == false ) { + if ( t == Datagram ) { +#if !defined(QT_NO_IPV6) + struct sockaddr_storage aa; +#else + struct sockaddr_in aa; +#endif + memset( &aa, 0, sizeof(aa) ); + QT_SOCKLEN_T sz; + sz = sizeof( aa ); + r = ::recvfrom( fd, data, maxlen, 0, + (struct sockaddr *)&aa, &sz ); + + qt_socket_getportaddr( (struct sockaddr *)&aa, &pp, &pa); + + } else { + r = ::read( fd, data, maxlen ); + } + done = true; + if ( r == 0 && t == Stream && maxlen > 0 ) { + // connection closed + close(); + } else if ( r >= 0 || errno == EAGAIN || errno == EWOULDBLOCK ) { + // nothing + } else if ( errno == EINTR ) { + done = false; + } else if ( e == NoError ) { + switch( errno ) { + case EIO: + case EISDIR: + case EBADF: + case EINVAL: + case EFAULT: + case ENOTCONN: + case ENOTSOCK: + e = Impossible; + break; +#if defined(ENONET) + case ENONET: +#endif + case EHOSTUNREACH: + case ENETDOWN: + case ENETUNREACH: + case ETIMEDOUT: + e = NetworkFailure; + break; + case EPIPE: + case ECONNRESET: + // connection closed + close(); + r = 0; + break; + default: + e = UnknownError; + break; + } + } + } + return r; +} + + +qint64 Q3SocketDevice::writeData( const char *data, qint64 len ) +{ + if ( data == 0 && len != 0 ) { +#if defined(QT_CHECK_NULL) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isValid() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Invalid socket" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Device is not open" ); +#endif + return -1; + } + if ( !isWritable() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Write operation not permitted" ); +#endif + return -1; + } + bool done = false; + int r = 0; + bool timeout; + while ( !done ) { + r = ::write( fd, data, len ); + done = true; + if ( r < 0 && e == NoError && + errno != EAGAIN && errno != EWOULDBLOCK ) { + switch( errno ) { + case EINTR: // signal - call read() or whatever again + done = false; + break; + case EPIPE: + case ECONNRESET: + // connection closed + close(); + r = 0; + break; + case ENOSPC: + case EIO: + case EISDIR: + case EBADF: + case EINVAL: + case EFAULT: + case ENOTCONN: + case ENOTSOCK: + e = Impossible; + break; +#if defined(ENONET) + case ENONET: +#endif + case EHOSTUNREACH: + case ENETDOWN: + case ENETUNREACH: + case ETIMEDOUT: + e = NetworkFailure; + break; + default: + e = UnknownError; + break; + } + } else if ( waitForMore( 0, &timeout ) == 0 ) { + if ( !timeout ) { + // connection closed + close(); + } + } + } + return r; +} + + +Q_LONG Q3SocketDevice::writeBlock( const char * data, Q_ULONG len, + const QHostAddress & host, Q_UINT16 port ) +{ + if ( t != Datagram ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Not datagram" ); +#endif + return -1; // for now - later we can do t/tcp + } + + if ( data == 0 && len != 0 ) { +#if defined(QT_CHECK_NULL) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isValid() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Invalid socket" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Device is not open" ); +#endif + return -1; + } + if ( !isWritable() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Write operation not permitted" ); +#endif + return -1; + } + struct sockaddr_in a4; + struct sockaddr *aa; + QT_SOCKLEN_T slen; +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 a6; + if ( host.isIPv6Address() ) { + memset( &a6, 0, sizeof(a6) ); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons( port ); + + Q_IPV6ADDR tmp = host.toIPv6Address(); + memcpy( &a6.sin6_addr.s6_addr, &tmp, sizeof(tmp) ); + slen = sizeof( a6 ); + aa = (struct sockaddr *)&a6; + } else +#endif + if ( host.isIPv4Address() ) { + memset( &a4, 0, sizeof(a4) ); + a4.sin_family = AF_INET; + a4.sin_port = htons( port ); + a4.sin_addr.s_addr = htonl( host.toIPv4Address() ); + slen = sizeof(a4); + aa = (struct sockaddr *)&a4; + } else { + e = Impossible; + return -1; + } + + // we'd use MSG_DONTWAIT + MSG_NOSIGNAL if Stevens were right. + // but apparently Stevens and most implementors disagree + bool done = false; + int r = 0; + while ( !done ) { + r = ::sendto( fd, data, len, 0, aa, slen); + done = true; + if ( r < 0 && e == NoError && + errno != EAGAIN && errno != EWOULDBLOCK ) { + switch( errno ) { + case EINTR: // signal - call read() or whatever again + done = false; + break; + case ENOSPC: + case EPIPE: + case EIO: + case EISDIR: + case EBADF: + case EINVAL: + case EFAULT: + case ENOTCONN: + case ENOTSOCK: + e = Impossible; + break; +#if defined(ENONET) + case ENONET: +#endif + case EHOSTUNREACH: + case ENETDOWN: + case ENETUNREACH: + case ETIMEDOUT: + e = NetworkFailure; + break; + default: + e = UnknownError; + break; + } + } + } + return r; +} + + +void Q3SocketDevice::fetchConnectionParameters() +{ + if ( !isValid() ) { + p = 0; + a = QHostAddress(); + pp = 0; + pa = QHostAddress(); + return; + } +#if !defined(QT_NO_IPV6) + struct sockaddr_storage sa; +#else + struct sockaddr_in sa; +#endif + memset( &sa, 0, sizeof(sa) ); + QT_SOCKLEN_T sz; + sz = sizeof( sa ); + if ( !::getsockname( fd, (struct sockaddr *)(&sa), &sz ) ) + qt_socket_getportaddr( (struct sockaddr *)&sa, &p, &a ); + + sz = sizeof( sa ); + if ( !::getpeername( fd, (struct sockaddr *)(&sa), &sz ) ) + qt_socket_getportaddr( (struct sockaddr *)&sa, &pp, &pa ); +} + + +Q_UINT16 Q3SocketDevice::peerPort() const +{ + return pp; +} + + +QHostAddress Q3SocketDevice::peerAddress() const +{ + return pa; +} + +QT_END_NAMESPACE + +#endif //QT_NO_NETWORK diff --git a/src/qt3support/network/q3socketdevice_win.cpp b/src/qt3support/network/q3socketdevice_win.cpp new file mode 100644 index 0000000..f2d034b --- /dev/null +++ b/src/qt3support/network/q3socketdevice_win.cpp @@ -0,0 +1,1068 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3socketdevice.h" +#include "qwindowdefs.h" +#include "qdatetime.h" + +#include <qcoreapplication.h> + +#include <string.h> + +#if defined (QT_NO_IPV6) +# include <windows.h> +# include <winsock.h> +#else +# if defined (Q_CC_BOR) || defined (Q_CC_GNU) +# include <winsock2.h> +# elif defined (Q_CC_INTEL) +# include <winsock.h> +# else +# include <windows.h> +# if defined(Q_OS_WINCE) +# include <winsock.h> +# endif +# endif +// Use our own defines and structs which we know are correct +# define QT_SS_MAXSIZE 128 +# define QT_SS_ALIGNSIZE (sizeof(__int64)) +# define QT_SS_PAD1SIZE (QT_SS_ALIGNSIZE - sizeof (short)) +# define QT_SS_PAD2SIZE (QT_SS_MAXSIZE - (sizeof (short) + QT_SS_PAD1SIZE + QT_SS_ALIGNSIZE)) + +QT_BEGIN_NAMESPACE + +struct qt_sockaddr_storage { + short ss_family; + char __ss_pad1[QT_SS_PAD1SIZE]; + __int64 __ss_align; + char __ss_pad2[QT_SS_PAD2SIZE]; +}; + +// sockaddr_in6 size changed between old and new SDK +// Only the new version is the correct one, so always +// use this structure. +struct qt_in6_addr { + u_char qt_s6_addr[16]; +}; +typedef struct { + short sin6_family; /* AF_INET6 */ + u_short sin6_port; /* Transport level port number */ + u_long sin6_flowinfo; /* IPv6 flow information */ + struct qt_in6_addr sin6_addr; /* IPv6 address */ + u_long sin6_scope_id; /* set of interfaces for a scope */ +} qt_sockaddr_in6; +#endif + +#ifndef AF_INET6 +#define AF_INET6 23 /* Internetwork Version 6 */ +#endif + +#ifndef NO_ERRNO_H +QT_BEGIN_INCLUDE_NAMESPACE +# if defined(Q_OS_WINCE) +# include "qfunctions_wince.h" +# else +# include <errno.h> +# endif +QT_END_INCLUDE_NAMESPACE +#endif + + +#if defined(SOCKLEN_T) +#undef SOCKLEN_T +#endif + +#define SOCKLEN_T int // #### Winsock 1.1 + +static int initialized = 0x00; // Holds the Winsock version + +static void cleanupWinSock() // post-routine +{ + WSACleanup(); + initialized = 0x00; +} + +static inline void qt_socket_getportaddr( struct sockaddr *sa, + quint16 *port, QHostAddress *addr ) +{ +#if !defined (QT_NO_IPV6) + if (sa->sa_family == AF_INET6) { + qt_sockaddr_in6 *sa6 = (qt_sockaddr_in6 *)sa; + Q_IPV6ADDR tmp; + for ( int i = 0; i < 16; ++i ) + tmp.c[i] = sa6->sin6_addr.qt_s6_addr[i]; + QHostAddress a( tmp ); + *addr = a; + *port = ntohs( sa6->sin6_port ); + return; + } +#endif + struct sockaddr_in *sa4 = (struct sockaddr_in *)sa; + QHostAddress a( ntohl( sa4->sin_addr.s_addr ) ); + *port = ntohs( sa4->sin_port ); + *addr = a; +} + +void Q3SocketDevice::init() +{ +#if !defined(QT_NO_IPV6) + if ( !initialized ) { + WSAData wsadata; + // IPv6 requires Winsock v2.0 or better. + if ( WSAStartup( MAKEWORD(2,0), &wsadata ) != 0 ) { +# if defined(QSOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice: WinSock v2.0 initialization failed, disabling IPv6 support." ); +# endif + } else { + qAddPostRoutine( cleanupWinSock ); + initialized = 0x20; + return; + } + } +#endif + + if (!initialized) { + WSAData wsadata; + if ( WSAStartup( MAKEWORD(1,1), &wsadata ) != 0 ) { +#if defined(QT_CHECK_NULL) + qWarning( "Q3SocketDevice: WinSock initialization failed" ); +#endif +#if defined(QSOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice: WinSock initialization failed" ); +#endif + return; + } + qAddPostRoutine( cleanupWinSock ); + initialized = 0x11; + } +} + +Q3SocketDevice::Protocol Q3SocketDevice::getProtocol() const +{ + if ( isValid() ) { +#if !defined (QT_NO_IPV6) + struct qt_sockaddr_storage sa; +#else + struct sockaddr_in sa; +#endif + memset( &sa, 0, sizeof(sa) ); + SOCKLEN_T sz = sizeof( sa ); + if ( !::getsockname(fd, (struct sockaddr *)&sa, &sz) ) { +#if !defined (QT_NO_IPV6) + switch ( sa.ss_family ) { + case AF_INET: + return IPv4; + case AF_INET6: + return IPv6; + default: + return Unknown; + } +#else + switch ( sa.sin_family ) { + case AF_INET: + return IPv4; + default: + return Unknown; + } +#endif + } + } + return Unknown; +} + +int Q3SocketDevice::createNewSocket( ) +{ +#if !defined(QT_NO_IPV6) + SOCKET s; + // Support IPv6 for Winsock v2.0++ + if ( initialized >= 0x20 && protocol() == IPv6 ) { + s = ::socket( AF_INET6, t==Datagram?SOCK_DGRAM:SOCK_STREAM, 0 ); + } else { + s = ::socket( AF_INET, t==Datagram?SOCK_DGRAM:SOCK_STREAM, 0 ); + } +#else + SOCKET s = ::socket( AF_INET, t==Datagram?SOCK_DGRAM:SOCK_STREAM, 0 ); +#endif + if ( s == INVALID_SOCKET ) { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + // ### what to use here? + e = NetworkFailure; + //e = Inaccessible; + break; + case WSAEMFILE: + e = NoFiles; // special case for this + break; + case WSAEINPROGRESS: + case WSAENOBUFS: + e = NoResources; + break; + case WSAEAFNOSUPPORT: + case WSAEPROTOTYPE: + case WSAEPROTONOSUPPORT: + case WSAESOCKTNOSUPPORT: + e = InternalError; + break; + default: + e = UnknownError; + break; + } + } else { + return s; + } + return -1; +} + + +void Q3SocketDevice::close() +{ + if ( fd == -1 || !isOpen() ) // already closed + return; + resetStatus(); + setOpenMode(NotOpen); + ::closesocket( fd ); +#if defined(QSOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice::close: Closed socket %x", fd ); +#endif + fd = -1; + fetchConnectionParameters(); + QIODevice::close(); +} + + +bool Q3SocketDevice::blocking() const +{ + return true; +} + + +void Q3SocketDevice::setBlocking( bool enable ) +{ +#if defined(QSOCKETDEVICE_DEBUG) + qDebug( "Q3SocketDevice::setBlocking( %d )", enable ); +#endif + if ( !isValid() ) + return; + + unsigned long dummy = enable ? 0 : 1; + ioctlsocket( fd, FIONBIO, &dummy ); +} + + +int Q3SocketDevice::option( Option opt ) const +{ + if ( !isValid() ) + return -1; + int n = -1; + int v = -1; + switch ( opt ) { + case Broadcast: + n = SO_BROADCAST; + break; + case ReceiveBuffer: + n = SO_RCVBUF; + break; + case ReuseAddress: + n = SO_REUSEADDR; + break; + case SendBuffer: + n = SO_SNDBUF; + break; + } + if ( n != -1 ) { + SOCKLEN_T len = sizeof(v); + int r = ::getsockopt( fd, SOL_SOCKET, n, (char*)&v, &len ); + if ( r != SOCKET_ERROR ) + return v; + if ( !e ) { + Q3SocketDevice *that = (Q3SocketDevice*)this; // mutable function + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + that->e = Impossible; + break; + case WSAENETDOWN: + that->e = NetworkFailure; + break; + case WSAEFAULT: + case WSAEINVAL: + case WSAENOPROTOOPT: + that->e = InternalError; + break; + case WSAEINPROGRESS: + that->e = NoResources; + break; + case WSAENOTSOCK: + that->e = Impossible; + break; + default: + that->e = UnknownError; + break; + } + } + return -1; + } + return v; +} + + +void Q3SocketDevice::setOption( Option opt, int v ) +{ + if ( !isValid() ) + return; + int n = -1; // for really, really bad compilers + switch ( opt ) { + case Broadcast: + n = SO_BROADCAST; + break; + case ReceiveBuffer: + n = SO_RCVBUF; + break; + case ReuseAddress: + n = SO_REUSEADDR; + break; + case SendBuffer: + n = SO_SNDBUF; + break; + default: + return; + } + int r = ::setsockopt( fd, SOL_SOCKET, n, (char*)&v, sizeof(v) ); + if ( r == SOCKET_ERROR && e == NoError ) { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + e = NetworkFailure; + break; + case WSAEFAULT: + case WSAEINVAL: + case WSAENOPROTOOPT: + e = InternalError; + break; + case WSAEINPROGRESS: + e = NoResources; + break; + case WSAENETRESET: + case WSAENOTCONN: + e = Impossible; // ### ? + break; + case WSAENOTSOCK: + e = Impossible; + break; + default: + e = UnknownError; + break; + } + } +} + + +bool Q3SocketDevice::connect( const QHostAddress &addr, quint16 port ) +{ + if ( !isValid() ) + return false; + + pa = addr; + pp = port; + + struct sockaddr_in a4; + struct sockaddr *aa; + SOCKLEN_T aalen; + +#if !defined(QT_NO_IPV6) + qt_sockaddr_in6 a6; + + if ( initialized >= 0x20 && addr.isIPv6Address() ) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons( port ); + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + memcpy( &a6.sin6_addr.qt_s6_addr, &ip6, sizeof(ip6) ); + + aalen = sizeof( a6 ); + aa = (struct sockaddr *)&a6; + } else +#endif + if ( addr.isIPv4Address() ) { + memset(&a4, 0, sizeof(a4)); + a4.sin_family = AF_INET; + a4.sin_port = htons(port); + a4.sin_addr.s_addr = htonl(addr.toIPv4Address()); + + aalen = sizeof(a4); + aa = (struct sockaddr *)&a4; + } else { + e = Impossible; + return false; + } + + int r = ::connect( fd, aa, aalen ); + + if ( r == SOCKET_ERROR ) + { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + e = NetworkFailure; + break; + case WSAEADDRINUSE: + case WSAEINPROGRESS: + case WSAENOBUFS: + e = NoResources; + break; + case WSAEINTR: + e = UnknownError; // ### ? + break; + case WSAEALREADY: + // ### ? + break; + case WSAEADDRNOTAVAIL: + e = ConnectionRefused; // ### ? + break; + case WSAEAFNOSUPPORT: + case WSAEFAULT: + e = InternalError; + break; + case WSAEINVAL: + break; + case WSAECONNREFUSED: + e = ConnectionRefused; + break; + case WSAEISCONN: + goto successful; + case WSAENETUNREACH: + case WSAETIMEDOUT: + e = NetworkFailure; + break; + case WSAENOTSOCK: + e = Impossible; + break; + case WSAEWOULDBLOCK: + break; + case WSAEACCES: + e = Inaccessible; + break; + case 10107: + // Workaround for a problem with the WinSock Proxy Server. See + // also support/arc-12/25557 for details on the problem. + goto successful; + default: + e = UnknownError; + break; + } + return false; + } +successful: + fetchConnectionParameters(); + return true; +} + + +bool Q3SocketDevice::bind( const QHostAddress &address, quint16 port ) +{ + if ( !isValid() ) + return false; + int r; + struct sockaddr_in a4; +#if !defined(QT_NO_IPV6) + qt_sockaddr_in6 a6; + + if ( initialized >= 0x20 && address.isIPv6Address() ) { + memset( &a6, 0, sizeof(a6) ); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons( port ); + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy( &a6.sin6_addr.qt_s6_addr, &tmp, sizeof(tmp) ); + + r = ::bind( fd, (struct sockaddr *)&a6, sizeof(struct qt_sockaddr_storage) ); + } else +#endif + if ( address.isIPv4Address() ) { + memset( &a4, 0, sizeof(a4) ); + a4.sin_family = AF_INET; + a4.sin_port = htons( port ); + a4.sin_addr.s_addr = htonl( address.toIPv4Address() ); + + r = ::bind( fd, (struct sockaddr*)&a4, sizeof(struct sockaddr_in) ); + } else { + e = Impossible; + return false; + } + + if ( r == SOCKET_ERROR ) { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + e = NetworkFailure; + break; + case WSAEACCES: + e = Inaccessible; + break; + case WSAEADDRNOTAVAIL: + e = Inaccessible; + break; + case WSAEFAULT: + e = InternalError; + break; + case WSAEINPROGRESS: + case WSAENOBUFS: + e = NoResources; + break; + case WSAEADDRINUSE: + case WSAEINVAL: + e = AlreadyBound; + break; + case WSAENOTSOCK: + e = Impossible; + break; + default: + e = UnknownError; + break; + } + return false; + } + fetchConnectionParameters(); + return true; +} + + +bool Q3SocketDevice::listen( int backlog ) +{ + if ( !isValid() ) + return false; + if ( ::listen( fd, backlog ) >= 0 ) + return true; + if ( !e ) + e = Impossible; + return false; +} + + +int Q3SocketDevice::accept() +{ + if ( !isValid() ) + return -1; +#if !defined(QT_NO_IPV6) + struct qt_sockaddr_storage a; +#else + struct sockaddr a; +#endif + SOCKLEN_T l = sizeof(a); + bool done; + SOCKET s; + do { + s = ::accept( fd, (struct sockaddr*)&a, &l ); + // we'll blithely throw away the stuff accept() wrote to a + done = true; + if ( s == INVALID_SOCKET && e == NoError ) { + switch( WSAGetLastError() ) { + case WSAEINTR: + done = false; + break; + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + case WSAEOPNOTSUPP: + // in all these cases, an error happened during connection + // setup. we're not interested in what happened, so we + // just treat it like the client-closed-quickly case. + break; + case WSAEFAULT: + e = InternalError; + break; + case WSAEMFILE: + case WSAEINPROGRESS: + case WSAENOBUFS: + e = NoResources; + break; + case WSAEINVAL: + case WSAENOTSOCK: + e = Impossible; + break; + case WSAEWOULDBLOCK: + break; + default: + e = UnknownError; + break; + } + } + } while (!done); + return s; +} + + +qint64 Q3SocketDevice::bytesAvailable() const +{ + if ( !isValid() ) + return -1; + u_long nbytes = 0; + if ( ::ioctlsocket(fd, FIONREAD, &nbytes) < 0 ) + return -1; + + // ioctlsocket sometimes reports 1 byte available for datagrams + // while the following recvfrom returns -1 and claims connection + // was reset (udp is connectionless). so we peek one byte to + // catch this case and return 0 bytes available if recvfrom + // fails. + if (nbytes == 1 && t == Datagram) { + char c; + if (::recvfrom(fd, &c, sizeof(c), MSG_PEEK, 0, 0) == SOCKET_ERROR) + return 0; + } + + return nbytes; +} + + +Q_LONG Q3SocketDevice::waitForMore( int msecs, bool *timeout ) const +{ + if ( !isValid() ) + return -1; + + fd_set fds; + memset(&fds, 0, sizeof(fd_set)); + fds.fd_count = 1; + fds.fd_array[0] = fd; + + struct timeval tv; + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; + + int rv = select( fd+1, &fds, 0, 0, msecs < 0 ? 0 : &tv ); + + if ( rv < 0 ) + return -1; + + if ( timeout ) { + if ( rv == 0 ) + *timeout = true; + else + *timeout = false; + } + + return bytesAvailable(); +} + + +qint64 Q3SocketDevice::readData( char *data, qint64 maxlen ) +{ +#if defined(QT_CHECK_NULL) + if ( data == 0 && maxlen != 0 ) { + qWarning( "Q3SocketDevice::readBlock: Null pointer error" ); + } +#endif +#if defined(QT_CHECK_STATE) + if ( !isValid() ) { + qWarning( "Q3SocketDevice::readBlock: Invalid socket" ); + return -1; + } + if ( !isOpen() ) { + qWarning( "Q3SocketDevice::readBlock: Device is not open" ); + return -1; + } + if ( !isReadable() ) { + qWarning( "Q3SocketDevice::readBlock: Read operation not permitted" ); + return -1; + } +#endif + qint64 r = 0; + if ( t == Datagram ) { +#if !defined(QT_NO_IPV6) + // With IPv6 support, we must be prepared to receive both IPv4 + // and IPv6 packets. The generic SOCKADDR_STORAGE (struct + // sockaddr_storage on unix) replaces struct sockaddr. + struct qt_sockaddr_storage a; +#else + struct sockaddr_in a; +#endif + memset( &a, 0, sizeof(a) ); + SOCKLEN_T sz; + sz = sizeof( a ); + r = ::recvfrom( fd, data, maxlen, 0, (struct sockaddr *)&a, &sz ); + qt_socket_getportaddr( (struct sockaddr *)(&a), &pp, &pa ); + } else { + r = ::recv( fd, data, maxlen, 0 ); + } + if ( r == 0 && t == Stream && maxlen > 0 ) { + if ( WSAGetLastError() != WSAEWOULDBLOCK ) { + // connection closed + close(); + } + } else if ( r == SOCKET_ERROR && e == NoError ) { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAECONNABORTED: + close(); + r = 0; + break; + case WSAETIMEDOUT: + case WSAECONNRESET: + /* + From msdn doc: + On a UDP datagram socket this error would indicate that a previous + send operation resulted in an ICMP "Port Unreachable" message. + + So we should not close this socket just because one sendto failed. + */ + if ( t != Datagram ) + close(); // connection closed + r = 0; + break; + case WSAENETDOWN: + case WSAENETRESET: + e = NetworkFailure; + break; + case WSAEFAULT: + case WSAENOTCONN: + case WSAESHUTDOWN: + case WSAEINVAL: + e = Impossible; + break; + case WSAEINTR: + // ### ? + r = 0; + break; + case WSAEINPROGRESS: + e = NoResources; + break; + case WSAENOTSOCK: + e = Impossible; + break; + case WSAEOPNOTSUPP: + e = InternalError; // ### ? + break; + case WSAEWOULDBLOCK: + break; + case WSAEMSGSIZE: + e = NoResources; // ### ? + break; + case WSAEISCONN: + // ### ? + r = 0; + break; + default: + e = UnknownError; + break; + } + } + return r; +} + + +qint64 Q3SocketDevice::writeData( const char *data, qint64 len ) +{ + if ( data == 0 && len != 0 ) { +#if defined(QT_CHECK_NULL) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isValid() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Invalid socket" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Device is not open" ); +#endif + return -1; + } + if ( !isWritable() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::writeBlock: Write operation not permitted" ); +#endif + return -1; + } + bool done = false; + qint64 r = 0; + while ( !done ) { + // Don't write more than 64K (see Knowledge Base Q201213). + r = ::send( fd, data, ( len>64*1024 ? 64*1024 : len ), 0 ); + done = true; + if ( r == SOCKET_ERROR && e == NoError ) {//&& errno != WSAEAGAIN ) { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + case WSAEACCES: + case WSAENETRESET: + case WSAESHUTDOWN: + case WSAEHOSTUNREACH: + e = NetworkFailure; + break; + case WSAECONNABORTED: + case WSAECONNRESET: + // connection closed + close(); + r = 0; + break; + case WSAEINTR: + done = false; + break; + case WSAEINPROGRESS: + e = NoResources; + // ### perhaps try it later? + break; + case WSAEFAULT: + case WSAEOPNOTSUPP: + e = InternalError; + break; + case WSAENOBUFS: + // ### try later? + break; + case WSAEMSGSIZE: + e = NoResources; + break; + case WSAENOTCONN: + case WSAENOTSOCK: + case WSAEINVAL: + e = Impossible; + break; + case WSAEWOULDBLOCK: + r = 0; + break; + default: + e = UnknownError; + break; + } + } + } + return r; +} + + +Q_LONG Q3SocketDevice::writeBlock( const char * data, Q_ULONG len, + const QHostAddress & host, quint16 port ) +{ + if ( t != Datagram ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Not datagram" ); +#endif + return -1; // for now - later we can do t/tcp + } + + if ( data == 0 && len != 0 ) { +#if defined(QT_CHECK_NULL) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isValid() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Invalid socket" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Device is not open" ); +#endif + return -1; + } + if ( !isWritable() ) { +#if defined(QT_CHECK_STATE) || defined(QSOCKETDEVICE_DEBUG) + qWarning( "Q3SocketDevice::sendBlock: Write operation not permitted" ); +#endif + return -1; + } + struct sockaddr_in a4; + struct sockaddr *aa; + SOCKLEN_T slen; +#if !defined(QT_NO_IPV6) + qt_sockaddr_in6 a6; + if ( initialized >= 0x20 && host.isIPv6Address() ) { + memset( &a6, 0, sizeof(a6) ); + a6.sin6_family = AF_INET6; + a6.sin6_port = htons( port ); + + Q_IPV6ADDR tmp = host.toIPv6Address(); + memcpy( &a6.sin6_addr.qt_s6_addr, &tmp, sizeof(tmp) ); + slen = sizeof( a6 ); + aa = (struct sockaddr *)&a6; + } else +#endif + if ( host.isIPv4Address() ) { + + memset( &a4, 0, sizeof(a4) ); + a4.sin_family = AF_INET; + a4.sin_port = htons( port ); + a4.sin_addr.s_addr = htonl( host.toIPv4Address() ); + slen = sizeof(a4); + aa = (struct sockaddr *)&a4; + } else { + e = Impossible; + return -1; + } + + // we'd use MSG_DONTWAIT + MSG_NOSIGNAL if Stevens were right. + // but apparently Stevens and most implementors disagree + bool done = false; + qint64 r = 0; + while ( !done ) { + r = ::sendto( fd, data, len, 0, aa, slen ); + done = true; + if ( r == SOCKET_ERROR && e == NoError ) {//&& e != EAGAIN ) { + switch( WSAGetLastError() ) { + case WSANOTINITIALISED: + e = Impossible; + break; + case WSAENETDOWN: + case WSAEACCES: + case WSAENETRESET: + case WSAESHUTDOWN: + case WSAEHOSTUNREACH: + case WSAECONNABORTED: + case WSAECONNRESET: + case WSAEADDRNOTAVAIL: + case WSAENETUNREACH: + case WSAETIMEDOUT: + e = NetworkFailure; + break; + case WSAEINTR: + done = false; + break; + case WSAEINPROGRESS: + e = NoResources; + // ### perhaps try it later? + break; + case WSAEFAULT: + case WSAEOPNOTSUPP: + case WSAEAFNOSUPPORT: + e = InternalError; + break; + case WSAENOBUFS: + case WSAEMSGSIZE: + e = NoResources; + break; + case WSAENOTCONN: + case WSAENOTSOCK: + case WSAEINVAL: + case WSAEDESTADDRREQ: + e = Impossible; + break; + case WSAEWOULDBLOCK: + r = 0; + break; + default: + e = UnknownError; + break; + } + } + } + return r; +} + + +void Q3SocketDevice::fetchConnectionParameters() +{ + if ( !isValid() ) { + p = 0; + a = QHostAddress(); + pp = 0; + pa = QHostAddress(); + return; + } +#if !defined (QT_NO_IPV6) + struct qt_sockaddr_storage sa; +#else + struct sockaddr_in sa; +#endif + memset( &sa, 0, sizeof(sa) ); + SOCKLEN_T sz; + sz = sizeof( sa ); + if ( !::getsockname( fd, (struct sockaddr *)(&sa), &sz ) ) + qt_socket_getportaddr( (struct sockaddr *)(&sa), &p, &a ); + pp = 0; + pa = QHostAddress(); +} + + +void Q3SocketDevice::fetchPeerConnectionParameters() +{ + // do the getpeername() lazy on Windows (sales/arc-18/37759 claims that + // there will be problems otherwise if you use MS Proxy server) +#if !defined (QT_NO_IPV6) + struct qt_sockaddr_storage sa; +#else + struct sockaddr_in sa; +#endif + memset( &sa, 0, sizeof(sa) ); + SOCKLEN_T sz; + sz = sizeof( sa ); + if ( !::getpeername( fd, (struct sockaddr *)(&sa), &sz ) ) + qt_socket_getportaddr( (struct sockaddr *)(&sa), &pp, &pa ); +} + +quint16 Q3SocketDevice::peerPort() const +{ + if ( pp==0 && isValid() ) { + Q3SocketDevice *that = (Q3SocketDevice*)this; // mutable + that->fetchPeerConnectionParameters(); + } + return pp; +} + + +QHostAddress Q3SocketDevice::peerAddress() const +{ + if ( pp==0 && isValid() ) { + Q3SocketDevice *that = (Q3SocketDevice*)this; // mutable + that->fetchPeerConnectionParameters(); + } + return pa; +} + +QT_END_NAMESPACE diff --git a/src/qt3support/network/q3url.cpp b/src/qt3support/network/q3url.cpp new file mode 100644 index 0000000..fc2fdb2 --- /dev/null +++ b/src/qt3support/network/q3url.cpp @@ -0,0 +1,1319 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3url.h" + +#ifndef QT_NO_URL + +#include "q3cstring.h" +#include "qdir.h" + +QT_BEGIN_NAMESPACE + +// used by q3filedialog.cpp +bool qt_resolve_symlinks = true; + +class Q3UrlPrivate +{ +public: + QString protocol; + QString user; + QString pass; + QString host; + QString path, cleanPath; + QString refEncoded; + QString queryEncoded; + bool isValid; + int port; + bool cleanPathDirty; +}; + +/*! + Replaces backslashes with slashes and removes multiple occurrences + of slashes or backslashes if \c allowMultiple is false. +*/ + +static void slashify( QString& s, bool allowMultiple = true ) +{ + bool justHadSlash = false; + for ( int i = 0; i < (int)s.length(); i++ ) { + if ( !allowMultiple && justHadSlash && + ( s[ i ] == QLatin1Char('/') || s[ i ] == QLatin1Char('\\') ) ) { + s.remove( i, 1 ); + --i; + continue; + } + if ( s[ i ] == QLatin1Char('\\') ) + s[ i ] = QLatin1Char('/'); +#if defined (Q_WS_MAC9) + if ( s[ i ] == QLatin1Char(':') && (i == (int)s.length()-1 || s[ i + 1 ] != QLatin1Char('/') ) ) //mac colon's go away, unless after a protocol + s[ i ] = QLatin1Char('/'); +#endif + if ( s[ i ] == QLatin1Char('/') ) + justHadSlash = true; + else + justHadSlash = false; + } +} + + + +/*! + \class Q3Url + \brief The Q3Url class provides a URL parser and simplifies working with URLs. + + \compat + + The Q3Url class is provided for simple work with URLs. It can + parse, decode, encode, etc. + + Q3Url works with the decoded path and encoded query in turn. + + Example: + + <tt>http://qtsoftware.com:80/cgi-bin/test%20me.pl?cmd=Hello%20you</tt> + + \table + \header \i Function \i Returns + \row \i \l protocol() \i "http" + \row \i \l host() \i "qtsoftware.com" + \row \i \l port() \i 80 + \row \i \l path() \i "/cgi-bin/test me.pl" + \row \i \l fileName() \i "test me.pl" + \row \i \l query() \i "cmd=Hello%20you" + \endtable + + Example: + + <tt>http://doc.trolltech.com/qdockarea.html#lines</tt> + + \table + \header \i Function \i Returns + \row \i \l protocol() \i "http" + \row \i \l host() \i "doc.trolltech.com" + \row \i \l fileName() \i "qdockarea.html" + \row \i \l ref() \i "lines" + \endtable + + The individual parts of a URL can be set with setProtocol(), + setHost(), setPort(), setPath(), setFileName(), setRef() and + setQuery(). A URL could contain, for example, an ftp address which + requires a user name and password; these can be set with setUser() + and setPassword(). + + Because path is always encoded internally you must not use "%00" + in the path, although this is okay (but not recommended) for the + query. + + Q3Url is normally used like this: + + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 0 + + You can then access and manipulate the various parts of the URL. + + To make it easy to work with Q3Urls and QStrings, Q3Url implements + the necessary cast and assignment operators so you can do + following: + + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 1 + + Use the static functions, encode() and decode() to encode or + decode a URL in a string. (They operate on the string in-place.) + The isRelativeUrl() static function returns true if the given + string is a relative URL. + + If you want to use a URL to work on a hierarchical structure (e.g. + a local or remote filesystem), you might want to use the subclass + Q3UrlOperator. + + \sa Q3UrlOperator +*/ + + +/*! + Constructs an empty URL that is invalid. +*/ + +Q3Url::Q3Url() +{ + d = new Q3UrlPrivate; + d->isValid = false; + d->port = -1; + d->cleanPathDirty = true; +} + +/*! + Constructs a URL by parsing the string \a url. + + If you pass a string like "/home/qt", the "file" protocol is + assumed. +*/ + +Q3Url::Q3Url( const QString& url ) +{ + d = new Q3UrlPrivate; + d->protocol = QLatin1String("file"); + d->port = -1; + parse( url ); +} + +/*! + Copy constructor. Copies the data of \a url. +*/ + +Q3Url::Q3Url( const Q3Url& url ) +{ + d = new Q3UrlPrivate; + *d = *url.d; +} + +/*! + Returns true if \a url is relative; otherwise returns false. +*/ + +bool Q3Url::isRelativeUrl( const QString &url ) +{ + int colon = url.find( QLatin1String(":") ); + int slash = url.find( QLatin1String("/") ); + + return ( slash != 0 && ( colon == -1 || ( slash != -1 && colon > slash ) ) ); +} + +/*! + Constructs an URL taking \a url as the base (context) and + \a relUrl as a relative URL to \a url. If \a relUrl is not relative, + \a relUrl is taken as the new URL. + + For example, the path of + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 2 + will be "/qt/srource/qt-2.1.0.tar.gz". + + On the other hand, + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 3 + will result in a new URL, "ftp://ftp.trolltech.com/usr/local", + because "/usr/local" isn't relative. + + Similarly, + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 4 + will result in a new URL, with "/usr/local" as the path + and "file" as the protocol. + + Normally it is expected that the path of \a url points to a + directory, even if the path has no slash at the end. But if you + want the constructor to handle the last part of the path as a file + name if there is no slash at the end, and to let it be replaced by + the file name of \a relUrl (if it contains one), set \a checkSlash + to true. +*/ + +Q3Url::Q3Url( const Q3Url& url, const QString& relUrl, bool checkSlash ) +{ + d = new Q3UrlPrivate; + QString rel = relUrl; + slashify( rel ); + + Q3Url urlTmp( url ); + if ( !urlTmp.isValid() ) { + urlTmp.reset(); + } + if ( isRelativeUrl( rel ) ) { + if ( rel[ 0 ] == QLatin1Char('#') ) { + *this = urlTmp; + rel.remove( (uint)0, 1 ); + decode( rel ); + setRef( rel ); + } else if ( rel[ 0 ] == QLatin1Char('?') ) { + *this = urlTmp; + rel.remove( (uint)0, 1 ); + setQuery( rel ); + } else { + decode( rel ); + *this = urlTmp; + setRef( QString() ); + if ( checkSlash && d->cleanPath[(int)path().length()-1] != QLatin1Char('/') ) { + if ( isRelativeUrl( path() ) ) + setEncodedPathAndQuery( rel ); + else + setFileName( rel ); + } else { + QString p = urlTmp.path(); + if ( p.isEmpty() ) { + // allow URLs like "file:foo" + if ( !d->host.isEmpty() && !d->user.isEmpty() && !d->pass.isEmpty() ) + p = QLatin1String("/"); + } + if ( !p.isEmpty() && p.right(1)!=QLatin1String("/") ) + p += QLatin1String("/"); + p += rel; + d->path = p; + d->cleanPathDirty = true; + } + } + } else { + if ( rel[ 0 ] == QChar( QLatin1Char('/') ) ) { + *this = urlTmp; + setEncodedPathAndQuery( rel ); + } else { + *this = rel; + } + } +} + +/*! + Destructor. +*/ + +Q3Url::~Q3Url() +{ + delete d; + d = 0; +} + +/*! + Returns the protocol of the URL. Typically, "file", "http", "ftp", + etc. + + \sa setProtocol() +*/ + +QString Q3Url::protocol() const +{ + return d->protocol; +} + +/*! + Sets the protocol of the URL to \a protocol. Typically, "file", + "http", "ftp", etc. + + \sa protocol() +*/ + +void Q3Url::setProtocol( const QString& protocol ) +{ + d->protocol = protocol; + if ( hasHost() ) + d->isValid = true; +} + +/*! + Returns the username of the URL. + + \sa setUser() setPassword() +*/ + +QString Q3Url::user() const +{ + return d->user; +} + +/*! + Sets the username of the URL to \a user. + + \sa user() setPassword() +*/ + +void Q3Url::setUser( const QString& user ) +{ + d->user = user; +} + +/*! + Returns true if the URL contains a username; otherwise returns + false. + + \sa setUser() setPassword() +*/ + +bool Q3Url::hasUser() const +{ + return !d->user.isEmpty(); +} + +/*! + Returns the password of the URL. + + \warning Passwords passed in URLs are normally \e insecure; this + is due to the mechanism, not because of Qt. + + \sa setPassword() setUser() +*/ + +QString Q3Url::password() const +{ + return d->pass; +} + +/*! + Sets the password of the URL to \a pass. + + \warning Passwords passed in URLs are normally \e insecure; this + is due to the mechanism, not because of Qt. + + \sa password() setUser() +*/ + +void Q3Url::setPassword( const QString& pass ) +{ + d->pass = pass; +} + +/*! + Returns true if the URL contains a password; otherwise returns + false. + + \warning Passwords passed in URLs are normally \e insecure; this + is due to the mechanism, not because of Qt. + + \sa setPassword() setUser() +*/ + +bool Q3Url::hasPassword() const +{ + return !d->pass.isEmpty(); +} + +/*! + Returns the hostname of the URL. + + \sa setHost() hasHost() +*/ + +QString Q3Url::host() const +{ + return d->host; +} + +/*! + Sets the hostname of the URL to \a host. + + \sa host() hasHost() +*/ + +void Q3Url::setHost( const QString& host ) +{ + d->host = host; + if ( !d->protocol.isNull() && d->protocol != QLatin1String("file") ) + d->isValid = true; +} + +/*! + Returns true if the URL contains a hostname; otherwise returns + false. + + \sa setHost() +*/ + +bool Q3Url::hasHost() const +{ + return !d->host.isEmpty(); +} + +/*! + Returns the port of the URL or -1 if no port has been set. + + \sa setPort() +*/ + +int Q3Url::port() const +{ + return d->port; +} + +/*! + Sets the port of the URL to \a port. + + \sa port() +*/ + +void Q3Url::setPort( int port ) +{ + d->port = port; +} + +/*! + Returns true if the URL contains a port; otherwise returns false. + + \sa setPort() +*/ + +bool Q3Url::hasPort() const +{ + return d->port >= 0; +} + +/*! + Sets the path of the URL to \a path. + + \sa path() hasPath() +*/ + +void Q3Url::setPath( const QString& path ) +{ + d->path = path; + slashify( d->path ); + d->cleanPathDirty = true; + d->isValid = true; +} + +/*! + Returns true if the URL contains a path; otherwise returns false. + + \sa path() setPath() +*/ + +bool Q3Url::hasPath() const +{ + return !d->path.isEmpty(); +} + +/*! + Sets the query of the URL to \a txt. \a txt must be encoded. + + \sa query() encode() +*/ + +void Q3Url::setQuery( const QString& txt ) +{ + d->queryEncoded = txt; +} + +/*! + Returns the (encoded) query of the URL. + + \sa setQuery() decode() +*/ + +QString Q3Url::query() const +{ + return d->queryEncoded; +} + +/*! + Returns the (encoded) reference of the URL. + + \sa setRef() hasRef() decode() +*/ + +QString Q3Url::ref() const +{ + return d->refEncoded; +} + +/*! + Sets the reference of the URL to \a txt. \a txt must be encoded. + + \sa ref() hasRef() encode() +*/ + +void Q3Url::setRef( const QString& txt ) +{ + d->refEncoded = txt; +} + +/*! + Returns true if the URL has a reference; otherwise returns false. + + \sa setRef() +*/ + +bool Q3Url::hasRef() const +{ + return !d->refEncoded.isEmpty(); +} + +/*! + Returns true if the URL is valid; otherwise returns false. A URL + is invalid if it cannot be parsed, for example. +*/ + +bool Q3Url::isValid() const +{ + return d->isValid; +} + +/*! + Resets all parts of the URL to their default values and + invalidates it. +*/ + +void Q3Url::reset() +{ + d->protocol = QLatin1String("file"); + d->user = QLatin1String(""); + d->pass = QLatin1String(""); + d->host = QLatin1String(""); + d->path = QLatin1String(""); + d->queryEncoded = QLatin1String(""); + d->refEncoded = QLatin1String(""); + d->isValid = true; + d->port = -1; + d->cleanPathDirty = true; +} + +/*! + Parses the \a url. Returns true on success; otherwise returns false. +*/ + +bool Q3Url::parse( const QString& url ) +{ + QString url_( url ); + slashify( url_ ); + + if ( url_.isEmpty() ) { + d->isValid = false; + return false; + } + + d->cleanPathDirty = true; + d->isValid = true; + QString oldProtocol = d->protocol; + d->protocol.clear(); + + const int Init = 0; + const int Protocol = 1; + const int Separator1= 2; // : + const int Separator2= 3; // :/ + const int Separator3= 4; // :// or more slashes + const int User = 5; + const int Pass = 6; + const int Host = 7; + const int Path = 8; + const int Ref = 9; + const int Query = 10; + const int Port = 11; + const int Done = 12; + + const int InputAlpha= 1; + const int InputDigit= 2; + const int InputSlash= 3; + const int InputColon= 4; + const int InputAt = 5; + const int InputHash = 6; + const int InputQuery= 7; + + static uchar table[ 12 ][ 8 ] = { + /* None InputAlpha InputDigit InputSlash InputColon InputAt InputHash InputQuery */ + { 0, Protocol, 0, Path, 0, 0, 0, 0, }, // Init + { 0, Protocol, Protocol, 0, Separator1, 0, 0, 0, }, // Protocol + { 0, Path, Path, Separator2, 0, 0, 0, 0, }, // Separator1 + { 0, Path, Path, Separator3, 0, 0, 0, 0, }, // Separator2 + { 0, User, User, Separator3, Pass, Host, 0, 0, }, // Separator3 + { 0, User, User, User, Pass, Host, User, User, }, // User + { 0, Pass, Pass, Pass, Pass, Host, Pass, Pass, }, // Pass + { 0, Host, Host, Path, Port, Host, Ref, Query, }, // Host + { 0, Path, Path, Path, Path, Path, Ref, Query, }, // Path + { 0, Ref, Ref, Ref, Ref, Ref, Ref, Query, }, // Ref + { 0, Query, Query, Query, Query, Query, Query, Query, }, // Query + { 0, 0, Port, Path, 0, 0, 0, 0, } // Port + }; + + bool relPath = false; + + relPath = false; + bool forceRel = false; + + // If ':' is at pos 1, we have only one letter + // before that separator => that's a drive letter! + if ( url_.length() >= 2 && url_[1] == QLatin1Char(':') ) + relPath = forceRel = true; + + int hasNoHost = -1; + int cs = url_.find( QLatin1String(":/") ); + if ( cs != -1 ) // if a protocol is there, find out if there is a host or directly the path after it + hasNoHost = url_.find( QLatin1String("///"), cs ); + table[ 4 ][ 1 ] = User; + table[ 4 ][ 2 ] = User; + if ( cs == -1 || forceRel ) { // we have a relative file + if ( url.find( QLatin1Char(':') ) == -1 || forceRel ) { + table[ 0 ][ 1 ] = Path; + // Filenames may also begin with a digit + table[ 0 ][ 2 ] = Path; + } else { + table[ 0 ][ 1 ] = Protocol; + } + relPath = true; + } else { // some checking + table[ 0 ][ 1 ] = Protocol; + + // find the part between the protocol and the path as the meaning + // of that part is dependent on some chars + ++cs; + while ( url_[ cs ] == QLatin1Char('/') ) + ++cs; + int slash = url_.find( QLatin1String("/"), cs ); + if ( slash == -1 ) + slash = url_.length() - 1; + QString tmp = url_.mid( cs, slash - cs + 1 ); + + if ( !tmp.isEmpty() ) { // if this part exists + + // look for the @ in this part + int at = tmp.find( QLatin1String("@") ); + if ( at != -1 ) + at += cs; + // we have no @, which means host[:port], so directly + // after the protocol the host starts, or if the protocol + // is file or there were more than 2 slashes, it is the + // path + if ( at == -1 ) { + if ( url_.left( 4 ) == QLatin1String("file") || hasNoHost != -1 ) + table[ 4 ][ 1 ] = Path; + else + table[ 4 ][ 1 ] = Host; + table[ 4 ][ 2 ] = table[ 4 ][ 1 ]; + } + } + } + + int state = Init; // parse state + int input; // input token + + QChar c = url_[ 0 ]; + int i = 0; + QString port; + + for ( ;; ) { + switch ( c.latin1() ) { + case '?': + input = InputQuery; + break; + case '#': + input = InputHash; + break; + case '@': + input = InputAt; + break; + case ':': + input = InputColon; + break; + case '/': + input = InputSlash; + break; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '0': + input = InputDigit; + break; + default: + input = InputAlpha; + } + + state = table[ state ][ input ]; + + switch ( state ) { + case Protocol: + d->protocol += c; + break; + case User: + d->user += c; + break; + case Pass: + d->pass += c; + break; + case Host: + d->host += c; + break; + case Path: + d->path += c; + break; + case Ref: + d->refEncoded += c; + break; + case Query: + d->queryEncoded += c; + break; + case Port: + port += c; + break; + default: + break; + } + + ++i; + if ( i > (int)url_.length() - 1 || state == Done || state == 0 ) + break; + c = url_[ i ]; + + } + + if ( !port.isEmpty() ) { + port.remove( (uint)0, 1 ); + d->port = port.toInt(); + } + + // error + if ( i < (int)url_.length() - 1 ) { + d->isValid = false; + return false; + } + + + if ( d->protocol.isEmpty() ) + d->protocol = oldProtocol; + + if ( d->path.isEmpty() ) + d->path = QLatin1String("/"); + + // hack for windows + if ( d->path.length() == 2 && d->path[ 1 ] == QLatin1Char(':') ) + d->path += QLatin1String("/"); + + // #### do some corrections, should be done nicer too + if ( !d->pass.isEmpty() ) { + if ( d->pass[ 0 ] == QLatin1Char(':') ) + d->pass.remove( (uint)0, 1 ); + decode( d->pass ); + } + if ( !d->user.isEmpty() ) { + decode( d->user ); + } + if ( !d->path.isEmpty() ) { + if ( d->path[ 0 ] == QLatin1Char('@') || d->path[ 0 ] == QLatin1Char(':') ) + d->path.remove( (uint)0, 1 ); + if ( d->path[ 0 ] != QLatin1Char('/') && !relPath && d->path[ 1 ] != QLatin1Char(':') ) + d->path.prepend( QLatin1String("/") ); + } + if ( !d->refEncoded.isEmpty() && d->refEncoded[ 0 ] == QLatin1Char('#') ) + d->refEncoded.remove( (uint)0, 1 ); + if ( !d->queryEncoded.isEmpty() && d->queryEncoded[ 0 ] == QLatin1Char('?') ) + d->queryEncoded.remove( (uint)0, 1 ); + if ( !d->host.isEmpty() && d->host[ 0 ] == QLatin1Char('@') ) + d->host.remove( (uint)0, 1 ); + +#if defined(Q_OS_WIN32) + // hack for windows file://machine/path syntax + if ( d->protocol == QLatin1String("file") ) { + if ( url.left( 7 ) == QLatin1String("file://") && + d->path.length() > 1 && d->path[ 1 ] != QLatin1Char(':') ) + d->path.prepend( QLatin1String("/") ); + } +#endif + + decode( d->path ); + d->cleanPathDirty = true; + +#if 0 + qDebug( "URL: %s", url.latin1() ); + qDebug( "protocol: %s", d->protocol.latin1() ); + qDebug( "user: %s", d->user.latin1() ); + qDebug( "pass: %s", d->pass.latin1() ); + qDebug( "host: %s", d->host.latin1() ); + qDebug( "path: %s", path().latin1() ); + qDebug( "ref: %s", d->refEncoded.latin1() ); + qDebug( "query: %s", d->queryEncoded.latin1() ); + qDebug( "port: %d\n\n----------------------------\n\n", d->port ); +#endif + + return true; +} + +/*! + \overload + + Parses \a url and assigns the resulting data to this class. + + If you pass a string like "/home/qt" the "file" protocol will be + assumed. +*/ + +Q3Url& Q3Url::operator=( const QString& url ) +{ + reset(); + parse( url ); + + return *this; +} + +/*! + Assigns the data of \a url to this class. +*/ + +Q3Url& Q3Url::operator=( const Q3Url& url ) +{ + *d = *url.d; + return *this; +} + +/*! + Compares this URL with \a url and returns true if they are equal; + otherwise returns false. +*/ + +bool Q3Url::operator==( const Q3Url& url ) const +{ + if ( !isValid() || !url.isValid() ) + return false; + + if ( d->protocol == url.d->protocol && + d->user == url.d->user && + d->pass == url.d->pass && + d->host == url.d->host && + d->path == url.d->path && + d->queryEncoded == url.d->queryEncoded && + d->refEncoded == url.d->refEncoded && + d->isValid == url.d->isValid && + d->port == url.d->port ) + return true; + + return false; +} + +/*! + \overload + + Compares this URL with \a url. \a url is parsed first. Returns + true if \a url is equal to this url; otherwise returns false. +*/ + +bool Q3Url::operator==( const QString& url ) const +{ + Q3Url u( url ); + return ( *this == u ); +} + +/*! + Sets the file name of the URL to \a name. If this URL contains a + fileName(), the original file name is replaced by \a name. + + See the documentation of fileName() for a more detailed discussion + of what is handled as file name and what is handled as a directory + path. + + \sa fileName() +*/ + +void Q3Url::setFileName( const QString& name ) +{ + QString fn( name ); + slashify( fn ); + + while ( fn[ 0 ] == QLatin1Char( '/' ) ) + fn.remove( (uint)0, 1 ); + + QString p; + if ( path().isEmpty() ) { + p = QLatin1String("/"); + } else { + p = path(); + int slash = p.findRev( QLatin1Char( '/' ) ); + if ( slash == -1 ) { + p = QLatin1String("/"); + } else if ( p[ (int)p.length() - 1 ] != QLatin1Char( '/' ) ) { + p.truncate( slash + 1 ); + } + } + + p += fn; + if ( !d->queryEncoded.isEmpty() ) + p += QLatin1String("?") + d->queryEncoded; + setEncodedPathAndQuery( p ); +} + +/*! + Returns the encoded path and query. + + \sa decode() +*/ + +QString Q3Url::encodedPathAndQuery() +{ + QString p = path(); + if ( p.isEmpty() ) + p = QLatin1String("/"); + + encode( p ); + + if ( !d->queryEncoded.isEmpty() ) { + p += QLatin1String("?"); + p += d->queryEncoded; + } + + return p; +} + +/*! + Parses \a pathAndQuery for a path and query and sets those values. + The whole string must be encoded. + + \sa encode() +*/ + +void Q3Url::setEncodedPathAndQuery( const QString& pathAndQuery ) +{ + d->cleanPathDirty = true; + int pos = pathAndQuery.find( QLatin1Char('?') ); + if ( pos == -1 ) { + d->path = pathAndQuery; + d->queryEncoded = QLatin1String(""); + } else { + d->path = pathAndQuery.left( pos ); + d->queryEncoded = pathAndQuery.mid( pos + 1 ); + } + + decode( d->path ); + d->cleanPathDirty = true; +} + +/*! + Returns the path of the URL. If \a correct is true, the path is + cleaned (deals with too many or too few slashes, cleans things + like "/../..", etc). Otherwise path() returns exactly the path + that was parsed or set. + + \sa setPath() hasPath() +*/ +QString Q3Url::path( bool correct ) const +{ + if ( !correct ) + return d->path; + + if ( d->cleanPathDirty ) { + bool check = true; + if ( QDir::isRelativePath( d->path ) ) { + d->cleanPath = d->path; + } else if ( isLocalFile() ) { +#if defined(Q_OS_WIN32) + // hack for stuff like \\machine\path and //machine/path on windows + if ( ( d->path.left( 1 ) == QLatin1String("/") || d->path.left( 1 ) == QLatin1String("\\") ) && + d->path.length() > 1 ) { + d->cleanPath = d->path; + bool share = (d->cleanPath[0] == QLatin1Char('\\') && d->cleanPath[1] == QLatin1Char('\\')) || + (d->cleanPath[0] == QLatin1Char('/') && d->cleanPath[1] == QLatin1Char('/')); + slashify( d->cleanPath, false ); + d->cleanPath = QDir::cleanDirPath( d->cleanPath ); + if ( share ) { + check = false; + while (d->cleanPath.at(0) != QLatin1Char('/') || d->cleanPath.at(1) != QLatin1Char('/')) + d->cleanPath.prepend(QLatin1String("/")); + } + } +#endif + if ( check ) { + QFileInfo fi( d->path ); + if ( !fi.exists() ) + d->cleanPath = d->path; + else if ( fi.isDir() ) { + QString canPath = QDir( d->path ).canonicalPath(); + QString dir; + if ( qt_resolve_symlinks && !canPath.isNull() ) + dir = QDir::cleanDirPath( canPath ); + else + dir = QDir::cleanDirPath( QDir( d->path ).absPath() ); + dir += QLatin1String("/"); + if ( dir == QLatin1String("//") ) + d->cleanPath = QLatin1String("/"); + else + d->cleanPath = dir; + } else { + QString p = + QDir::cleanDirPath( (qt_resolve_symlinks ? + fi.dir().canonicalPath() : + fi.dir().absPath()) ); + d->cleanPath = p + QLatin1String("/") + fi.fileName(); + } + } + } else { + if ( d->path != QLatin1String("/") && d->path[ (int)d->path.length() - 1 ] == QLatin1Char('/') ) + d->cleanPath = QDir::cleanDirPath( d->path ) + QLatin1String("/"); + else + d->cleanPath = QDir::cleanDirPath( d->path ); + } + + if ( check ) + slashify( d->cleanPath, false ); + d->cleanPathDirty = false; + } + + return d->cleanPath; +} + +/*! + Returns true if the URL is a local file; otherwise returns false. +*/ + +bool Q3Url::isLocalFile() const +{ + return d->protocol == QLatin1String("file"); +} + +/*! + Returns the file name of the URL. If the path of the URL doesn't + have a slash at the end, the part between the last slash and the + end of the path string is considered to be the file name. If the + path has a slash at the end, an empty string is returned here. + + \sa setFileName() +*/ + +QString Q3Url::fileName() const +{ + if ( d->path.isEmpty() || d->path.endsWith( QLatin1String("/") ) +#ifdef Q_WS_WIN + || d->path.endsWith( QLatin1String("\\") ) +#endif + ) + return QString(); + + return QFileInfo( d->path ).fileName(); +} + +/*! + Adds the path \a pa to the path of the URL. + + \sa setPath() hasPath() +*/ + +void Q3Url::addPath( const QString& pa ) +{ + if ( pa.isEmpty() ) + return; + + QString p( pa ); + slashify( p ); + + if ( path().isEmpty() ) { + if ( p[ 0 ] != QLatin1Char( '/' ) ) + d->path = QLatin1String("/") + p; + else + d->path = p; + } else { + if ( p[ 0 ] != QLatin1Char( '/' ) && d->path[ (int)d->path.length() - 1 ] != QLatin1Char('/') ) + d->path += QLatin1String("/") + p; + else + d->path += p; + } + d->cleanPathDirty = true; +} + +/*! + Returns the directory path of the URL. This is the part of the + path of the URL without the fileName(). See the documentation of + fileName() for a discussion of what is handled as file name and + what is handled as directory path. + + \sa setPath() hasPath() +*/ + +QString Q3Url::dirPath() const +{ + if ( path().isEmpty() ) + return QString(); + + QString s = path(); + int pos = s.findRev( QLatin1Char('/') ); + if ( pos == -1 ) { + return QString::fromLatin1( "." ); + } else { + if ( pos == 0 ) + return QString::fromLatin1( "/" ); + return s.left( pos ); + } +} + +/*! + Encodes the \a url in-place into UTF-8. For example + + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 5 + + \sa decode() +*/ + +void Q3Url::encode( QString& url ) +{ + if ( url.isEmpty() ) + return; + + Q3CString curl = url.utf8(); + int oldlen = curl.length(); + + const Q3CString special( "+<>#@\"&%$:,;?={}|^~[]\'`\\ \n\t\r" ); + QString newUrl; + int newlen = 0; + + for ( int i = 0; i < oldlen ;++i ) { + uchar inCh = (uchar)curl[ i ]; + + if ( inCh >= 128 || special.contains(inCh) ) { + newUrl[ newlen++ ] = QLatin1Char( '%' ); + + ushort c = inCh / 16; + c += c > 9 ? 'A' - 10 : '0'; + newUrl[ newlen++ ] = c; + + c = inCh % 16; + c += c > 9 ? 'A' - 10 : '0'; + newUrl[ newlen++ ] = c; + } else { + newUrl[ newlen++ ] = inCh; + } + } + + url = newUrl; +} + +static uchar hex_to_int( uchar c ) +{ + if ( c >= 'A' && c <= 'F' ) + return c - 'A' + 10; + if ( c >= 'a' && c <= 'f') + return c - 'a' + 10; + if ( c >= '0' && c <= '9') + return c - '0'; + return 0; +} + +/*! + Decodes the \a url in-place into UTF-8. For example + + \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 6 + + \sa encode() +*/ + +void Q3Url::decode( QString& url ) +{ + if ( url.isEmpty() ) + return; + + int newlen = 0; + Q3CString curl = url.utf8(); + int oldlen = curl.length(); + + Q3CString newUrl(oldlen); + + int i = 0; + while ( i < oldlen ) { + uchar c = (uchar)curl[ i++ ]; + if ( c == '%' && i <= oldlen - 2 ) { + c = hex_to_int( (uchar)curl[ i ] ) * 16 + hex_to_int( (uchar)curl[ i + 1 ] ); + i += 2; + } + newUrl [ newlen++ ] = c; + } + newUrl.truncate( newlen ); + + url = QString::fromUtf8(newUrl.data()); +} + + +/*! + Composes a string version of the URL and returns it. If \a + encodedPath is true the path in the returned string is encoded. If + \a forcePrependProtocol is true and \a encodedPath looks like a + local filename, the "file:/" protocol is also prepended. + + \sa encode() decode() +*/ + +QString Q3Url::toString( bool encodedPath, bool forcePrependProtocol ) const +{ + QString res, p = path(); + if ( encodedPath ) + encode( p ); + + if ( isLocalFile() ) { + if ( forcePrependProtocol ) + res = d->protocol + QLatin1String(":") + p; + else + res = p; + } else if ( d->protocol == QLatin1String("mailto") ) { + res = d->protocol + QLatin1String(":") + p; + } else { + res = d->protocol + QLatin1String("://"); + if ( !d->user.isEmpty() || !d->pass.isEmpty() ) { + QString tmp; + if ( !d->user.isEmpty() ) { + tmp = d->user; + encode( tmp ); + res += tmp; + } + if ( !d->pass.isEmpty() ) { + tmp = d->pass; + encode( tmp ); + res += QLatin1String(":") + tmp; + } + res += QLatin1String("@"); + } + res += d->host; + if ( d->port != -1 ) + res += QLatin1String(":") + QString( QLatin1String("%1") ).arg( d->port ); + if ( !p.isEmpty() ) { + if ( !d->host.isEmpty() && p[0]!= QLatin1Char( '/' ) ) + res += QLatin1String("/"); + res += p; + } + } + + if ( !d->refEncoded.isEmpty() ) + res += QLatin1String("#") + d->refEncoded; + if ( !d->queryEncoded.isEmpty() ) + res += QLatin1String("?") + d->queryEncoded; + + return res; +} + +/*! + Composes a string version of the URL and returns it. + + \sa Q3Url::toString() +*/ + +Q3Url::operator QString() const +{ + return toString(); +} + +/*! + Changes the directory to one directory up. This function always returns + true. + + \sa setPath() +*/ + +bool Q3Url::cdUp() +{ + d->path += QLatin1String("/.."); + d->cleanPathDirty = true; + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_URL diff --git a/src/qt3support/network/q3url.h b/src/qt3support/network/q3url.h new file mode 100644 index 0000000..61c25b1 --- /dev/null +++ b/src/qt3support/network/q3url.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3URL_H +#define Q3URL_H + +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_URL + +class Q3UrlPrivate; + +class Q_COMPAT_EXPORT Q3Url +{ +public: + Q3Url(); + Q3Url( const QString& url ); + Q3Url( const Q3Url& url ); + Q3Url( const Q3Url& url, const QString& relUrl, bool checkSlash = false ); + virtual ~Q3Url(); + + QString protocol() const; + virtual void setProtocol( const QString& protocol ); + + QString user() const; + virtual void setUser( const QString& user ); + bool hasUser() const; + + QString password() const; + virtual void setPassword( const QString& pass ); + bool hasPassword() const; + + QString host() const; + virtual void setHost( const QString& user ); + bool hasHost() const; + + int port() const; + virtual void setPort( int port ); + bool hasPort() const; + + QString path( bool correct = true ) const; + virtual void setPath( const QString& path ); + bool hasPath() const; + + virtual void setEncodedPathAndQuery( const QString& enc ); + QString encodedPathAndQuery(); + + virtual void setQuery( const QString& txt ); + QString query() const; + + QString ref() const; + virtual void setRef( const QString& txt ); + bool hasRef() const; + + bool isValid() const; + bool isLocalFile() const; + + virtual void addPath( const QString& path ); + virtual void setFileName( const QString& txt ); + + QString fileName() const; + QString dirPath() const; + + Q3Url& operator=( const Q3Url& url ); + Q3Url& operator=( const QString& url ); + + bool operator==( const Q3Url& url ) const; + bool operator==( const QString& url ) const; + + static void decode( QString& url ); + static void encode( QString& url ); + + operator QString() const; + virtual QString toString( bool encodedPath = false, bool forcePrependProtocol = true ) const; + + virtual bool cdUp(); + + static bool isRelativeUrl( const QString &url ); + +protected: + virtual void reset(); + virtual bool parse( const QString& url ); + +private: + Q3UrlPrivate *d; + +}; + +#endif // QT_NO_URL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3URL_H diff --git a/src/qt3support/network/q3urloperator.cpp b/src/qt3support/network/q3urloperator.cpp new file mode 100644 index 0000000..3f334a8 --- /dev/null +++ b/src/qt3support/network/q3urloperator.cpp @@ -0,0 +1,1212 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3urloperator.h" + +#ifndef QT_NO_NETWORKPROTOCOL + +#include "qurlinfo.h" +#include "q3networkprotocol.h" +#include "qmap.h" +#include "qdir.h" +#include "q3ptrdict.h" +#include "qpointer.h" +#include "q3valuelist.h" + +#include "qapplication.h" + +QT_BEGIN_NAMESPACE + +//#define Q3URLOPERATOR_DEBUG + +class Q3UrlOperatorPrivate +{ +public: + Q3UrlOperatorPrivate() + { + oldOps.setAutoDelete( false ); + networkProtocol = 0; + nameFilter = QLatin1String("*"); + currPut = 0; + } + + ~Q3UrlOperatorPrivate() + { + delete networkProtocol; + while ( oldOps.first() ) { + oldOps.first()->free(); + oldOps.removeFirst(); + } + } + + QMap<QString, QUrlInfo> entryMap; + Q3NetworkProtocol *networkProtocol; + QString nameFilter; + QDir dir; + + // maps needed for copy/move operations + Q3PtrDict<Q3NetworkOperation> getOpPutOpMap; + Q3PtrDict<Q3NetworkProtocol> getOpPutProtMap; + Q3PtrDict<Q3NetworkProtocol> getOpGetProtMap; + Q3PtrDict<Q3NetworkOperation> getOpRemoveOpMap; + QPointer<Q3NetworkProtocol> currPut; + QStringList waitingCopies; + QString waitingCopiesDest; + bool waitingCopiesMove; + Q3PtrList< Q3NetworkOperation > oldOps; +}; + +/*! + \class Q3UrlOperator + + \brief The Q3UrlOperator class provides common operations on URLs. + + \compat + + \module network + + This class operates on hierarchical structures (such as + filesystems) using URLs. Its API facilitates all the common + operations: + \table + \header \i Operation \i Function + \row \i List files \i \l listChildren() + \row \i Make a directory \i \l mkdir() + \row \i Remove a file \i \l remove() + \row \i Rename a file \i \l rename() + \row \i Get a file \i \l get() + \row \i Put a file \i \l put() + \row \i Copy a file \i \l copy() + \endtable + + You can obtain additional information about the URL with isDir() + and info(). If a directory is to be traversed using + listChildren(), a name filter can be set with setNameFilter(). + + A Q3UrlOperator can be used like this, for example to download a + file (and assuming that the FTP protocol is registered): + \snippet doc/src/snippets/code/src_qt3support_network_q3urloperator.cpp 0 + + If you want to be notified about success/failure, progress, etc., + you can connect to Q3UrlOperator's signals, e.g. to start(), + newChildren(), createdDirectory(), removed(), data(), + dataTransferProgress(), startedNextCopy(), + connectionStateChanged(), finished(), etc. A network operation can + be stopped with stop(). + + The class uses the functionality of registered network protocols + to perform these operations. Depending of the protocol of the URL, + it uses an appropriate network protocol class for the operations. + Each of the operation functions of Q3UrlOperator creates a + Q3NetworkOperation object that describes the operation and puts it + into the operation queue for the network protocol used. If no + suitable protocol could be found (because no implementation of the + necessary network protocol is registered), the URL operator emits + errors. Not every protocol supports every operation, but error + handling deals with this problem. + + To register the available network protocols, use the + qInitNetworkProtocols() function. The protocols currently + supported are: + \list + \i \link Q3Ftp FTP\endlink, + \i \link Q3Http HTTP\endlink, + \i \link Q3LocalFs local file system\endlink. + \endlist + + \sa Q3NetworkProtocol, Q3NetworkOperation +*/ + +/*! + \fn void Q3UrlOperator::newChildren( const Q3ValueList<QUrlInfo> &i, Q3NetworkOperation *op ) + + This signal is emitted after listChildren() was called and new + children (i.e. files) have been read from a list of files. \a i + holds the information about the new files. \a op is a pointer + to the operation object which contains all the information about + the operation, including the state. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + + +/*! + \fn void Q3UrlOperator::finished( Q3NetworkOperation *op ) + + This signal is emitted when an operation of some sort finishes, + whether with success or failure. \a op is a pointer to the + operation object, which contains all the information, including + the state, of the operation which has been finished. Check the + state and error code of the operation object to see whether or not + the operation was successful. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::start( Q3NetworkOperation *op ) + + Some operations (such as listChildren()) emit this signal when + they start processing the operation. \a op is a pointer to the + operation object which contains all the information about the + operation, including the state. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::createdDirectory( const QUrlInfo &i, Q3NetworkOperation *op ) + + This signal is emitted when mkdir() succeeds and the directory has + been created. \a i holds the information about the new directory. + + \a op is a pointer to the operation object, which contains all the + information about the operation, including the state. + \c op->arg(0) holds the new directory's name. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::removed( Q3NetworkOperation *op ) + + This signal is emitted when remove() has been successful and the + file has been removed. + + \a op is a pointer to the operation object which contains all the + information about the operation, including the state. + \c op->arg(0) holds the name of the file that was removed. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::itemChanged( Q3NetworkOperation *op ) + + This signal is emitted whenever a file which is a child of the URL + has been changed, for example by successfully calling rename(). + \a op is a pointer to the operation object which contains all the + information about the operation, including the state. + \c op->arg(0) holds the original file name and \c op->arg(1) holds + the new file name (if it was changed). + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::data( const QByteArray &data, Q3NetworkOperation *op ) + + This signal is emitted when new \a data has been received after calling + get() or put(). + \a op is a pointer to the operation object which contains all + the information about the operation, including the state. + \c op->arg(0) holds the name of the file whose data is retrieved + and op->rawArg(1) holds the (raw) data. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::dataTransferProgress( int bytesDone, int bytesTotal, Q3NetworkOperation *op ) + + This signal is emitted during data transfer (using put() or + get()). \a bytesDone specifies how many bytes of \a bytesTotal have + been transferred. More information about the operation is stored in + \a op, a pointer to the network operation that is processed. + \a bytesTotal may be -1, which means that the total number of bytes + is not known. + + \sa Q3NetworkOperation, Q3NetworkProtocol +*/ + +/*! + \fn void Q3UrlOperator::startedNextCopy( const Q3PtrList<Q3NetworkOperation> &lst ) + + This signal is emitted if copy() starts a new copy operation. \a + lst contains all Q3NetworkOperations related to this copy + operation. + + \sa copy() +*/ + +/*! + \fn void Q3UrlOperator::connectionStateChanged( int state, const QString &data ) + + This signal is emitted whenever the URL operator's connection + state changes. \a state describes the new state, which is a + \l{Q3NetworkProtocol::ConnectionState} value. + + \a data is a string that describes the change of the connection. + This can be used to display a message to the user. +*/ + +/*! + Constructs a Q3UrlOperator with an empty (i.e. invalid) URL. +*/ + +Q3UrlOperator::Q3UrlOperator() + : Q3Url() +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: cstr 1" ); +#endif + d = new Q3UrlOperatorPrivate; +} + +/*! + Constructs a Q3UrlOperator using \a url and parses this string. + + If you pass strings like "/home/qt" the "file" protocol is + assumed. +*/ + +Q3UrlOperator::Q3UrlOperator( const QString &url ) + : Q3Url( url ) +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: cstr 2" ); +#endif + d = new Q3UrlOperatorPrivate; + getNetworkProtocol(); +} + +/*! + Constructs a copy of \a url. +*/ + +Q3UrlOperator::Q3UrlOperator( const Q3UrlOperator& url ) + : QObject(), Q3Url( url ) +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: cstr 3" ); +#endif + d = new Q3UrlOperatorPrivate; + *d = *url.d; + + d->networkProtocol = 0; + getNetworkProtocol(); + d->nameFilter = QLatin1String("*"); + d->currPut = 0; +} + +/*! + Constructs a Q3UrlOperator. The URL on which this Q3UrlOperator + operates is constructed out of the arguments \a url, \a relUrl and + \a checkSlash: see the corresponding Q3Url constructor for an + explanation of these arguments. +*/ + +Q3UrlOperator::Q3UrlOperator( const Q3UrlOperator& url, const QString& relUrl, bool checkSlash ) + : Q3Url( url, relUrl, checkSlash ) +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: cstr 4" ); +#endif + d = new Q3UrlOperatorPrivate; + if ( relUrl == QLatin1String(".") ) + *d = *url.d; + + d->networkProtocol = 0; + getNetworkProtocol(); + d->currPut = 0; +} + +/*! + Destructor. +*/ + +Q3UrlOperator::~Q3UrlOperator() +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: dstr" ); +#endif + delete d; +} + +/*! + This private function is used by the simple operation functions, + i.e. listChildren(), mkdir(), remove(), rename(), get() and put(), + to really start the operation. \a op is a pointer to the network + operation that should be started. Returns \a op on success; + otherwise returns 0. +*/ +const Q3NetworkOperation *Q3UrlOperator::startOperation( Q3NetworkOperation *op ) +{ + if ( !d->networkProtocol ) + getNetworkProtocol(); + + if ( d->networkProtocol && (d->networkProtocol->supportedOperations()&op->operation()) ) { + d->networkProtocol->addOperation( op ); + if ( op->operation() == Q3NetworkProtocol::OpListChildren ) + clearEntries(); + return op; + } + + // error + QString msg; + if ( !d->networkProtocol ) { + msg = tr( "The protocol `%1' is not supported" ).arg( protocol() ); + } else { + switch ( op->operation() ) { + case Q3NetworkProtocol::OpListChildren: + msg = tr( "The protocol `%1' does not support listing directories" ).arg( protocol() ); + break; + case Q3NetworkProtocol::OpMkDir: + msg = tr( "The protocol `%1' does not support creating new directories" ).arg( protocol() ); + break; + case Q3NetworkProtocol::OpRemove: + msg = tr( "The protocol `%1' does not support removing files or directories" ).arg( protocol() ); + break; + case Q3NetworkProtocol::OpRename: + msg = tr( "The protocol `%1' does not support renaming files or directories" ).arg( protocol() ); + break; + case Q3NetworkProtocol::OpGet: + msg = tr( "The protocol `%1' does not support getting files" ).arg( protocol() ); + break; + case Q3NetworkProtocol::OpPut: + msg = tr( "The protocol `%1' does not support putting files" ).arg( protocol() ); + break; + default: + // this should never happen + break; + } + } + op->setState( Q3NetworkProtocol::StFailed ); + op->setProtocolDetail( msg ); + op->setErrorCode( (int)Q3NetworkProtocol::ErrUnsupported ); + emit finished( op ); + deleteOperation( op ); + return 0; +} + +/*! + Starts listing the children of this URL (e.g. the files in the + directory). The start() signal is emitted before the first entry + is listed and finished() is emitted after the last one. The + newChildren() signal is emitted for each list of new entries. If + an error occurs, the signal finished() is emitted, so be sure to + check the state of the network operation pointer. + + Because the operation may not be executed immediately, a pointer + to the Q3NetworkOperation object created by this function is + returned. This object contains all the data about the operation + and is used to refer to this operation later (e.g. in the signals + that are emitted by the Q3UrlOperator). The return value can also + be 0 if the operation object couldn't be created. + + The path of this Q3UrlOperator must to point to a directory + (because the children of this directory will be listed), not to a + file. +*/ + +const Q3NetworkOperation *Q3UrlOperator::listChildren() +{ + if ( !checkValid() ) + return 0; + + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpListChildren, QString(), QString(), QString() ); + return startOperation( res ); +} + +/*! + Tries to create a directory (child) with the name \a dirname. If + it is successful, a newChildren() signal with the new child is + emitted, and the createdDirectory() signal with the information + about the new child is also emitted. The finished() signal (with + success or failure) is emitted after the operation has been + processed, so check the state of the network operation object to + see whether or not the operation was successful. + + Because the operation will not be executed immediately, a pointer + to the Q3NetworkOperation object created by this function is + returned. This object contains all the data about the operation + and is used to refer to this operation later (e.g. in the signals + that are emitted by the Q3UrlOperator). The return value can also + be 0 if the operation object couldn't be created. + + The path of this Q3UrlOperator must to point to a directory (not a + file) because the new directory will be created in this path. +*/ + +const Q3NetworkOperation *Q3UrlOperator::mkdir( const QString &dirname ) +{ + if ( !checkValid() ) + return 0; + + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpMkDir, dirname, QString(), QString() ); + return startOperation( res ); +} + +/*! + Tries to remove the file (child) \a filename. If it succeeds the + removed() signal is emitted. finished() (with success or failure) + is also emitted after the operation has been processed, so check + the state of the network operation object to see whether or not + the operation was successful. + + Because the operation will not be executed immediately, a pointer + to the Q3NetworkOperation object created by this function is + returned. This object contains all the data about the operation + and is used to refer to this operation later (e.g. in the signals + that are emitted by the Q3UrlOperator). The return value can also + be 0 if the operation object couldn't be created. + + The path of this Q3UrlOperator must point to a directory; because + if \a filename is relative, it will try to remove it in this + directory. +*/ + +const Q3NetworkOperation *Q3UrlOperator::remove( const QString &filename ) +{ + if ( !checkValid() ) + return 0; + + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpRemove, filename, QString(), QString() ); + return startOperation( res ); +} + +/*! + Tries to rename the file (child) called \a oldname to \a newname. + If it succeeds, the itemChanged() signal is emitted. finished() + (with success or failure) is also emitted after the operation has + been processed, so check the state of the network operation object + to see whether or not the operation was successful. + + Because the operation may not be executed immediately, a pointer + to the Q3NetworkOperation object created by this function is + returned. This object contains all the data about the operation + and is used to refer to this operation later (e.g. in the signals + that are emitted by the Q3UrlOperator). The return value can also + be 0 if the operation object couldn't be created. + + This path of this Q3UrlOperator must to point to a directory + because \a oldname and \a newname are handled relative to this + directory. +*/ + +const Q3NetworkOperation *Q3UrlOperator::rename( const QString &oldname, const QString &newname ) +{ + if ( !checkValid() ) + return 0; + + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpRename, oldname, newname, QString() ); + return startOperation( res ); +} + +/*! + Copies the file \a from to \a to. If \a move is true, the file is + moved (copied and removed). \a from must point to a file and \a to + must point to a directory (into which \a from is copied) unless \a + toPath is set to false. If \a toPath is set to false then the \a + to variable is assumed to be the absolute file path (destination + file path + file name). The copying is done using the get() and + put() operations. If you want to be notified about the progress of + the operation, connect to the dataTransferProgress() signal. Bear + in mind that the get() and put() operations emit this signal + through the Q3UrlOperator. The number of transferred bytes and the + total bytes that you receive as arguments in this signal do not + relate to the the whole copy operation; they relate first to the + get() and then to the put() operation. Always check what type of + operation the signal comes from; this is given in the signal's + last argument. + + At the end, finished() (with success or failure) is emitted, so + check the state of the network operation object to see whether or + not the operation was successful. + + Because a move or copy operation consists of multiple operations + (get(), put() and maybe remove()), this function doesn't return a + single Q3NetworkOperation, but rather a list of them. They are in + the order: get(), put() and (if applicable) remove(). + + \sa get(), put() +*/ + +Q3PtrList<Q3NetworkOperation> Q3UrlOperator::copy( const QString &from, const QString &to, bool move, bool toPath ) +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: copy %s %s %d", from.latin1(), to.latin1(), move ); +#endif + + Q3PtrList<Q3NetworkOperation> ops; + ops.setAutoDelete( false ); + + Q3UrlOperator *uFrom = new Q3UrlOperator( *this, from ); + Q3UrlOperator *uTo = new Q3UrlOperator( to ); + + // prepare some string for later usage + QString frm = *uFrom; + QString file = uFrom->fileName(); + + if (frm == to + file) + return ops; + + file.prepend( QLatin1String("/") ); + + // uFrom and uTo are deleted when the Q3NetworkProtocol deletes itself via + // autodelete + uFrom->getNetworkProtocol(); + uTo->getNetworkProtocol(); + Q3NetworkProtocol *gProt = uFrom->d->networkProtocol; + Q3NetworkProtocol *pProt = uTo->d->networkProtocol; + + uFrom->setPath( uFrom->dirPath() ); + + if ( gProt && (gProt->supportedOperations()&Q3NetworkProtocol::OpGet) && + pProt && (pProt->supportedOperations()&Q3NetworkProtocol::OpPut) ) { + + connect( gProt, SIGNAL(data(QByteArray,Q3NetworkOperation*)), + this, SLOT(copyGotData(QByteArray,Q3NetworkOperation*)) ); + connect( gProt, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)), + this, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)) ); + connect( gProt, SIGNAL(finished(Q3NetworkOperation*)), + this, SLOT(continueCopy(Q3NetworkOperation*)) ); + connect( gProt, SIGNAL(finished(Q3NetworkOperation*)), + this, SIGNAL(finished(Q3NetworkOperation*)) ); + connect( gProt, SIGNAL(connectionStateChanged(int,QString)), + this, SIGNAL(connectionStateChanged(int,QString)) ); + + connect( pProt, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)), + this, SIGNAL(dataTransferProgress(int,int,Q3NetworkOperation*)) ); + connect( pProt, SIGNAL(finished(Q3NetworkOperation*)), + this, SIGNAL(finished(Q3NetworkOperation*)) ); + connect( pProt, SIGNAL(finished(Q3NetworkOperation*)), + this, SLOT(finishedCopy()) ); + + Q3NetworkOperation *opGet = new Q3NetworkOperation( Q3NetworkProtocol::OpGet, frm, QString(), QString() ); + ops.append( opGet ); + gProt->addOperation( opGet ); + + + QString toFile = to + file; + if (!toPath) + toFile = to; + + Q3NetworkOperation *opPut = new Q3NetworkOperation( Q3NetworkProtocol::OpPut, toFile, QString(), QString() ); + ops.append( opPut ); + + d->getOpPutProtMap.insert( (void*)opGet, pProt ); + d->getOpGetProtMap.insert( (void*)opGet, gProt ); + d->getOpPutOpMap.insert( (void*)opGet, opPut ); + + if ( move && (gProt->supportedOperations()&Q3NetworkProtocol::OpRemove) ) { + gProt->setAutoDelete( false ); + + Q3NetworkOperation *opRm = new Q3NetworkOperation( Q3NetworkProtocol::OpRemove, frm, QString(), QString() ); + ops.append( opRm ); + d->getOpRemoveOpMap.insert( (void*)opGet, opRm ); + } else { + gProt->setAutoDelete( true ); + } +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: copy operation should start now..." ); +#endif + return ops; + } else { + QString msg; + if ( !gProt ) { + msg = tr( "The protocol `%1' is not supported" ).arg( uFrom->protocol() ); + } else if ( gProt->supportedOperations() & Q3NetworkProtocol::OpGet ) { + msg = tr( "The protocol `%1' does not support copying or moving files or directories" ).arg( uFrom->protocol() ); + } else if ( !pProt ) { + msg = tr( "The protocol `%1' is not supported" ).arg( uTo->protocol() ); + } else { + msg = tr( "The protocol `%1' does not support copying or moving files or directories" ).arg( uTo->protocol() ); + } + delete uFrom; + delete uTo; + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpGet, frm, to, QString() ); + res->setState( Q3NetworkProtocol::StFailed ); + res->setProtocolDetail( msg ); + res->setErrorCode( (int)Q3NetworkProtocol::ErrUnsupported ); + emit finished( res ); + deleteOperation( res ); + } + + return ops; +} + +/*! + \overload + + Copies the \a files to the directory \a dest. If \a move is true + the files are moved, not copied. \a dest must point to a + directory. + + This function calls copy() for each entry in \a files in turn. You + don't get a result from this function; each time a new copy + begins, startedNextCopy() is emitted, with a list of + Q3NetworkOperations that describe the new copy operation. +*/ + +void Q3UrlOperator::copy( const QStringList &files, const QString &dest, + bool move ) +{ + d->waitingCopies = files; + d->waitingCopiesDest = dest; + d->waitingCopiesMove = move; + + finishedCopy(); +} + +/*! + Returns true if the URL is a directory; otherwise returns false. + This may not always work correctly, if the protocol of the URL is + something other than file (local filesystem). If you pass a bool + pointer as the \a ok argument, *\a ok is set to true if the result + of this function is known to be correct, and to false otherwise. +*/ + +bool Q3UrlOperator::isDir( bool *ok ) +{ + if ( ok ) + *ok = true; + if ( isLocalFile() ) { + if ( QFileInfo( path() ).isDir() ) + return true; + else + return false; + } + + if ( d->entryMap.contains( QLatin1String(".") ) ) { + return d->entryMap[ QLatin1String(".") ].isDir(); + } + // #### can assume that we are a directory? + if ( ok ) + *ok = false; + return true; +} + +/*! + Tells the network protocol to get data from \a location or, if + it is empty, to get data from the location to which this + URL points (see Q3Url::fileName() and Q3Url::encodedPathAndQuery()). + What happens then depends on the network protocol. The data() + signal is emitted when data comes in. Because it's unlikely that + all data will come in at once, it is common for multiple data() + signals to be emitted. The dataTransferProgress() signal is + emitted while processing the operation. At the end, finished() + (with success or failure) is emitted, so check the state of the + network operation object to see whether or not the operation was + successful. + + If \a location is empty, the path of this Q3UrlOperator + should point to a file when you use this operation. If \a location + is not empty, it can be a relative URL (a child of the path to + which the Q3UrlOperator points) or an absolute URL. + + For example, to get a web page you might do something like this: + + \snippet doc/src/snippets/code/src_qt3support_network_q3urloperator.cpp 1 + + For most other operations, the path of the Q3UrlOperator must point + to a directory. If you want to download a file you could do the + following: + + \snippet doc/src/snippets/code/src_qt3support_network_q3urloperator.cpp 2 + + This will get the data of ftp://ftp.whatever.org/pub/a_file.txt. + + \e Never do anything like this: + \snippet doc/src/snippets/code/src_qt3support_network_q3urloperator.cpp 3 + + If \a location is not empty and relative it must not contain any + queries or references, just the name of a child. So if you need to + specify a query or reference, do it as shown in the first example + or specify the full URL (such as + http://www.whatever.org/cgi-bin/search.pl?cmd=Hello) as \a location. + + \sa copy() +*/ + +const Q3NetworkOperation *Q3UrlOperator::get( const QString &location ) +{ + Q3Url u( *this ); + if ( !location.isEmpty() ) + u = Q3Url( *this, location ); + + if ( !u.isValid() ) + return 0; + + if ( !d->networkProtocol ) { + setProtocol( u.protocol() ); + getNetworkProtocol(); + } + + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpGet, u, QString(), QString() ); + return startOperation( res ); +} + +/*! + This function tells the network protocol to put \a data in \a + location. If \a location is empty, it puts the \a data in the + location to which the URL points. What happens depends on + the network protocol. Depending on the network protocol, some + data might come back after putting data, in which case the data() + signal is emitted. The dataTransferProgress() signal is emitted + during processing of the operation. At the end, finished() (with + success or failure) is emitted, so check the state of the network + operation object to see whether or not the operation was + successful. + + If \a location is empty, the path of this Q3UrlOperator should + point to a file when you use this operation. If \a location + is not empty, it can be a relative (a child of the path to which + the Q3UrlOperator points) or an absolute URL. + + For putting some data to a file you can do the following: + + \snippet doc/src/snippets/code/src_qt3support_network_q3urloperator.cpp 4 + + For most other operations, the path of the Q3UrlOperator must point + to a directory. If you want to upload data to a file you could do + the following: + + \snippet doc/src/snippets/code/src_qt3support_network_q3urloperator.cpp 5 + + This will upload the data to ftp://ftp.whatever.com/home/me/filename.dat. + + \sa copy() +*/ + +const Q3NetworkOperation *Q3UrlOperator::put( const QByteArray &data, const QString &location ) +{ + Q3Url u( *this ); + if ( !location.isEmpty() ) + u = Q3Url( *this, location ); + + if ( !u.isValid() ) + return 0; + + if ( !d->networkProtocol ) { + setProtocol( u.protocol() ); + getNetworkProtocol(); + } + + Q3NetworkOperation *res = new Q3NetworkOperation( Q3NetworkProtocol::OpPut, u, QString(), QString() ); + res->setRawArg( 1, data ); + return startOperation( res ); +} + +/*! + Sets the name filter of the URL to \a nameFilter. + + \sa QDir::setNameFilter() +*/ + +void Q3UrlOperator::setNameFilter( const QString &nameFilter ) +{ + d->nameFilter = nameFilter; +} + +/*! + Returns the name filter of the URL. + + \sa Q3UrlOperator::setNameFilter() QDir::nameFilter() +*/ + +QString Q3UrlOperator::nameFilter() const +{ + return d->nameFilter; +} + +/*! + Clears the cache of children. +*/ + +void Q3UrlOperator::clearEntries() +{ + d->entryMap.clear(); +} + +/*! + Adds an entry to the cache of children. +*/ + +void Q3UrlOperator::addEntry( const Q3ValueList<QUrlInfo> &i ) +{ + Q3ValueList<QUrlInfo>::ConstIterator it = i.begin(); + for ( ; it != i.end(); ++it ) + d->entryMap[ ( *it ).name().stripWhiteSpace() ] = *it; +} + +/*! + Returns the URL information for the child \a entry, or returns an + empty QUrlInfo object if there is no information available about + \a entry. Information about \a entry is only available after a successfully + finished listChildren() operation. +*/ + +QUrlInfo Q3UrlOperator::info( const QString &entry ) const +{ + if ( d->entryMap.contains( entry.stripWhiteSpace() ) ) { + return d->entryMap[ entry.stripWhiteSpace() ]; + } else if ( entry == QLatin1String(".") || entry == QLatin1String("..") ) { + // return a faked QUrlInfo + QUrlInfo inf; + inf.setName( entry ); + inf.setDir( true ); + inf.setFile( false ); + inf.setSymLink( false ); + inf.setOwner( tr( "(unknown)" ) ); + inf.setGroup( tr( "(unknown)" ) ); + inf.setSize( 0 ); + inf.setWritable( false ); + inf.setReadable( true ); + return inf; + } + return QUrlInfo(); +} + +/*! + Finds a network protocol for the URL and deletes the old network protocol. +*/ + +void Q3UrlOperator::getNetworkProtocol() +{ + delete d->networkProtocol; + Q3NetworkProtocol *p = Q3NetworkProtocol::getNetworkProtocol( protocol() ); + if ( !p ) { + d->networkProtocol = 0; + return; + } + + d->networkProtocol = (Q3NetworkProtocol *)p; + d->networkProtocol->setUrl( this ); + connect( d->networkProtocol, SIGNAL(itemChanged(Q3NetworkOperation*)), + this, SLOT(slotItemChanged(Q3NetworkOperation*)) ); +} + +/*! + Deletes the currently used network protocol. +*/ + +void Q3UrlOperator::deleteNetworkProtocol() +{ + if (d->networkProtocol) { + d->networkProtocol->deleteLater(); + d->networkProtocol = 0; + } +} + +/*! + \reimp +*/ + +void Q3UrlOperator::setPath( const QString& path ) +{ + Q3Url::setPath( path ); + if ( d->networkProtocol ) + d->networkProtocol->setUrl( this ); +} + +/*! + \reimp +*/ + +void Q3UrlOperator::reset() +{ + Q3Url::reset(); + deleteNetworkProtocol(); + d->nameFilter = QLatin1String("*"); +} + +/*! + \reimp +*/ + +bool Q3UrlOperator::parse( const QString &url ) +{ + bool b = Q3Url::parse( url ); + if ( !b ) { + return b; + } + + getNetworkProtocol(); + + return b; +} + +/*! + Assigns \a url to this object. +*/ + +Q3UrlOperator& Q3UrlOperator::operator=( const Q3UrlOperator &url ) +{ + deleteNetworkProtocol(); + Q3Url::operator=( url ); + + Q3PtrDict<Q3NetworkOperation> getOpPutOpMap = d->getOpPutOpMap; + Q3PtrDict<Q3NetworkProtocol> getOpPutProtMap = d->getOpPutProtMap; + Q3PtrDict<Q3NetworkProtocol> getOpGetProtMap = d->getOpGetProtMap; + Q3PtrDict<Q3NetworkOperation> getOpRemoveOpMap = d->getOpRemoveOpMap; + + *d = *url.d; + + d->oldOps.setAutoDelete( false ); + d->getOpPutOpMap = getOpPutOpMap; + d->getOpPutProtMap = getOpPutProtMap; + d->getOpGetProtMap = getOpGetProtMap; + d->getOpRemoveOpMap = getOpRemoveOpMap; + + d->networkProtocol = 0; + getNetworkProtocol(); + return *this; +} + +/*! + Assigns \a url to this object. +*/ + +Q3UrlOperator& Q3UrlOperator::operator=( const QString &url ) +{ + deleteNetworkProtocol(); + Q3Url::operator=( url ); + d->oldOps.setAutoDelete( false ); + getNetworkProtocol(); + return *this; +} + +/*! + \internal +*/ + +bool Q3UrlOperator::cdUp() +{ + bool b = Q3Url::cdUp(); + if ( d->networkProtocol ) + d->networkProtocol->setUrl( this ); + return b; +} + +/*! + \internal +*/ + +bool Q3UrlOperator::checkValid() +{ + // ###### + if ( !isValid() ) { + //emit error( ErrValid, tr( "The entered URL is not valid!" ) ); + return false; + } else + return true; +} + + +/*! + \internal +*/ + +void Q3UrlOperator::copyGotData( const QByteArray &data_, Q3NetworkOperation *op ) +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: copyGotData: %d new bytes", data_.size() ); +#endif + Q3NetworkOperation *put = d->getOpPutOpMap[ (void*)op ]; + if ( put ) { + QByteArray &s = put->raw( 1 ); + int size = s.size(); + s.resize( size + data_.size() ); + memcpy( s.data() + size, data_.data(), data_.size() ); + } + emit data( data_, op ); +} + +/*! + \internal +*/ + +void Q3UrlOperator::continueCopy( Q3NetworkOperation *op ) +{ + if ( op->operation() != Q3NetworkProtocol::OpGet ) + return; + if ( op->state()!=Q3NetworkProtocol::StDone && op->state()!=Q3NetworkProtocol::StFailed ) { + return; + } + +#ifdef Q3URLOPERATOR_DEBUG + if ( op->state() != Q3NetworkProtocol::StFailed ) { + qDebug( "Q3UrlOperator: continue copy (get finished, put will start)" ); + } +#endif + + Q3NetworkOperation *put = d->getOpPutOpMap[ (void*)op ]; + Q3NetworkProtocol *gProt = d->getOpGetProtMap[ (void*)op ]; + Q3NetworkProtocol *pProt = d->getOpPutProtMap[ (void*)op ]; + Q3NetworkOperation *rm = d->getOpRemoveOpMap[ (void*)op ]; + d->getOpPutOpMap.take( op ); + d->getOpGetProtMap.take( op ); + d->getOpPutProtMap.take( op ); + d->getOpRemoveOpMap.take( op ); + if ( pProt ) + pProt->setAutoDelete( true ); + if ( put && pProt ) { + if ( op->state() != Q3NetworkProtocol::StFailed ) { + pProt->addOperation( put ); + d->currPut = pProt; + if (rm) { // we need the result of the put operation + qApp->processEvents(); // process posted operations + if (put->state() == Q3NetworkProtocol::StFailed) { + deleteOperation( rm ); + rm = 0; + } + } + } else { + deleteOperation( put ); + } + } + if ( gProt ) { + gProt->setAutoDelete( true ); + } + if ( rm && gProt ) { + if ( op->state() != Q3NetworkProtocol::StFailed ) { + gProt->addOperation( rm ); + } else { + deleteOperation( rm ); + } + } + disconnect( gProt, SIGNAL(data(QByteArray,Q3NetworkOperation*)), + this, SLOT(copyGotData(QByteArray,Q3NetworkOperation*)) ); + disconnect( gProt, SIGNAL(finished(Q3NetworkOperation*)), + this, SLOT(continueCopy(Q3NetworkOperation*)) ); +} + +/*! + \internal +*/ + +void Q3UrlOperator::finishedCopy() +{ +#ifdef Q3URLOPERATOR_DEBUG + qDebug( "Q3UrlOperator: finished copy (finished putting)" ); +#endif + + if ( d->waitingCopies.isEmpty() ) + return; + + QString cp = d->waitingCopies.first(); + d->waitingCopies.remove( cp ); + Q3PtrList<Q3NetworkOperation> lst = copy( cp, d->waitingCopiesDest, d->waitingCopiesMove ); + emit startedNextCopy( lst ); +} + +/*! + Stops the current network operation and removes all this + Q3UrlOperator's waiting network operations. +*/ + +void Q3UrlOperator::stop() +{ + d->getOpPutOpMap.clear(); + d->getOpRemoveOpMap.clear(); + d->getOpGetProtMap.setAutoDelete( true ); + d->getOpPutProtMap.setAutoDelete( true ); + Q3PtrDictIterator<Q3NetworkProtocol> it( d->getOpPutProtMap ); + for ( ; it.current(); ++it ) + it.current()->stop(); + d->getOpPutProtMap.clear(); + it = Q3PtrDictIterator<Q3NetworkProtocol>( d->getOpGetProtMap ); + for ( ; it.current(); ++it ) + it.current()->stop(); + d->getOpGetProtMap.clear(); + if ( d->currPut ) { + d->currPut->stop(); + delete (Q3NetworkProtocol *) d->currPut; + d->currPut = 0; + } + d->waitingCopies.clear(); + if ( d->networkProtocol ) + d->networkProtocol->stop(); + getNetworkProtocol(); +} + +/*! + \internal +*/ + +void Q3UrlOperator::deleteOperation( Q3NetworkOperation *op ) +{ + if ( op ) + d->oldOps.append( op ); +} + +/*! + \internal + updates the entryMap after a network operation finished +*/ + +void Q3UrlOperator::slotItemChanged( Q3NetworkOperation *op ) +{ + if ( !op ) + return; + + switch ( op->operation() ) { + case Q3NetworkProtocol::OpRename : + { + if ( op->arg( 0 ) == op->arg( 1 ) ) + return; + + QMap<QString, QUrlInfo>::iterator mi = d->entryMap.find( op->arg( 0 ) ); + if ( mi != d->entryMap.end() ) { + mi.data().setName( op->arg( 1 ) ); + d->entryMap[ op->arg( 1 ) ] = mi.data(); + d->entryMap.erase( mi ); + } + break; + } + case Q3NetworkProtocol::OpRemove : + { + QMap<QString, QUrlInfo>::iterator mi = d->entryMap.find( op->arg( 0 ) ); + if ( mi != d->entryMap.end() ) + d->entryMap.erase( mi ); + break; + } + default: + break; + } +} + +QT_END_NAMESPACE + +#include "moc_q3urloperator.cpp" + +#endif // QT_NO_NETWORKPROTOCOL diff --git a/src/qt3support/network/q3urloperator.h b/src/qt3support/network/q3urloperator.h new file mode 100644 index 0000000..071e506 --- /dev/null +++ b/src/qt3support/network/q3urloperator.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3URLOPERATOR_H +#define Q3URLOPERATOR_H + +#include <QtCore/qobject.h> +#include <Qt3Support/q3url.h> +#include <Qt3Support/q3ptrlist.h> +#include <Qt3Support/q3networkprotocol.h> +#include <QtCore/qstringlist.h> // QString->QStringList conversion + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_NETWORKPROTOCOL + +class QUrlInfo; +class Q3UrlOperatorPrivate; +class Q3NetworkProtocol; + +class Q_COMPAT_EXPORT Q3UrlOperator : public QObject, public Q3Url +{ + Q_OBJECT + friend class Q3NetworkProtocol; + +public: + Q3UrlOperator(); + Q3UrlOperator( const QString &urL ); + Q3UrlOperator( const Q3UrlOperator& url ); + Q3UrlOperator( const Q3UrlOperator& url, const QString& relUrl, bool checkSlash = false ); + virtual ~Q3UrlOperator(); + + virtual void setPath( const QString& path ); + virtual bool cdUp(); + + virtual const Q3NetworkOperation *listChildren(); + virtual const Q3NetworkOperation *mkdir( const QString &dirname ); + virtual const Q3NetworkOperation *remove( const QString &filename ); + virtual const Q3NetworkOperation *rename( const QString &oldname, const QString &newname ); + virtual const Q3NetworkOperation *get( const QString &location = QString() ); + virtual const Q3NetworkOperation *put( const QByteArray &data, const QString &location = QString() ); + virtual Q3PtrList<Q3NetworkOperation> copy( const QString &from, const QString &to, bool move = false, bool toPath = true ); + virtual void copy( const QStringList &files, const QString &dest, bool move = false ); + virtual bool isDir( bool *ok = 0 ); + + virtual void setNameFilter( const QString &nameFilter ); + QString nameFilter() const; + + virtual QUrlInfo info( const QString &entry ) const; + + Q3UrlOperator& operator=( const Q3UrlOperator &url ); + Q3UrlOperator& operator=( const QString &url ); + + virtual void stop(); + +Q_SIGNALS: + void newChildren( const Q3ValueList<QUrlInfo> &, Q3NetworkOperation *res ); + void finished( Q3NetworkOperation *res ); + void start( Q3NetworkOperation *res ); + void createdDirectory( const QUrlInfo &, Q3NetworkOperation *res ); + void removed( Q3NetworkOperation *res ); + void itemChanged( Q3NetworkOperation *res ); + void data( const QByteArray &, Q3NetworkOperation *res ); + void dataTransferProgress( int bytesDone, int bytesTotal, Q3NetworkOperation *res ); + void startedNextCopy( const Q3PtrList<Q3NetworkOperation> &lst ); + void connectionStateChanged( int state, const QString &data ); + +protected: + void reset(); + bool parse( const QString& url ); + virtual bool checkValid(); + virtual void clearEntries(); + void getNetworkProtocol(); + void deleteNetworkProtocol(); + +private Q_SLOTS: + const Q3NetworkOperation *startOperation( Q3NetworkOperation *op ); + void copyGotData( const QByteArray &data, Q3NetworkOperation *op ); + void continueCopy( Q3NetworkOperation *op ); + void finishedCopy(); + void addEntry( const Q3ValueList<QUrlInfo> &i ); + void slotItemChanged( Q3NetworkOperation *op ); + +private: + void deleteOperation( Q3NetworkOperation *op ); + + Q3UrlOperatorPrivate *d; +}; + +#endif // QT_NO_NETWORKPROTOCOL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3URLOPERATOR_H |